Merge pull request #66351 from home-assistant/rc

This commit is contained in:
Paulus Schoutsen 2022-02-11 14:11:43 -08:00 committed by GitHub
commit fc2d30c993
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
43 changed files with 372 additions and 100 deletions

View File

@ -264,6 +264,7 @@ tests/components/enphase_envoy/* @gtdiehl
homeassistant/components/entur_public_transport/* @hfurubotten
homeassistant/components/environment_canada/* @gwww @michaeldavie
tests/components/environment_canada/* @gwww @michaeldavie
homeassistant/components/envisalink/* @ufodone
homeassistant/components/ephember/* @ttroy50
homeassistant/components/epson/* @pszafer
tests/components/epson/* @pszafer

View File

@ -3,7 +3,7 @@
"name": "Aseko Pool Live",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/aseko_pool_live",
"requirements": ["aioaseko==0.0.1"],
"requirements": ["aioaseko==0.0.2"],
"codeowners": [
"@milanmeu"
],

View File

@ -75,6 +75,7 @@ async def async_setup_august(
hass.config_entries.async_update_entry(config_entry, data=config_data)
await august_gateway.async_authenticate()
await august_gateway.async_refresh_access_token_if_needed()
hass.data.setdefault(DOMAIN, {})
data = hass.data[DOMAIN][config_entry.entry_id] = {
@ -106,11 +107,10 @@ class AugustData(AugustSubscriberMixin):
async def async_setup(self):
"""Async setup of august device data and activities."""
token = self._august_gateway.access_token
user_data, locks, doorbells = await asyncio.gather(
self._api.async_get_user(token),
self._api.async_get_operable_locks(token),
self._api.async_get_doorbells(token),
)
# This used to be a gather but it was less reliable with august's recent api changes.
user_data = await self._api.async_get_user(token)
locks = await self._api.async_get_operable_locks(token)
doorbells = await self._api.async_get_doorbells(token)
if not doorbells:
doorbells = []
if not locks:

View File

@ -2,7 +2,7 @@
"domain": "august",
"name": "August",
"documentation": "https://www.home-assistant.io/integrations/august",
"requirements": ["yalexs==1.1.20"],
"requirements": ["yalexs==1.1.22"],
"codeowners": ["@bdraco"],
"dhcp": [
{

View File

@ -222,7 +222,12 @@ async def async_get_mjpeg_stream(
"""Fetch an mjpeg stream from a camera entity."""
camera = _get_camera_from_entity_id(hass, entity_id)
return await camera.handle_async_mjpeg_stream(request)
try:
stream = await camera.handle_async_mjpeg_stream(request)
except ConnectionResetError:
stream = None
_LOGGER.debug("Error while writing MJPEG stream to transport")
return stream
async def async_get_still_stream(
@ -784,7 +789,11 @@ class CameraMjpegStream(CameraView):
async def handle(self, request: web.Request, camera: Camera) -> web.StreamResponse:
"""Serve camera stream, possibly with interval."""
if (interval_str := request.query.get("interval")) is None:
try:
stream = await camera.handle_async_mjpeg_stream(request)
except ConnectionResetError:
stream = None
_LOGGER.debug("Error while writing MJPEG stream to transport")
if stream is None:
raise web.HTTPBadGateway()
return stream

View File

@ -81,8 +81,8 @@ class CPUSpeedSensor(SensorEntity):
if info:
self._attr_extra_state_attributes = {
ATTR_ARCH: info["arch_string_raw"],
ATTR_BRAND: info["brand_raw"],
ATTR_ARCH: info.get("arch_string_raw"),
ATTR_BRAND: info.get("brand_raw"),
}
if HZ_ADVERTISED in info:
self._attr_extra_state_attributes[ATTR_HZ] = round(

View File

@ -137,6 +137,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
keep_alive,
hass.loop,
connection_timeout,
False,
)
hass.data[DATA_EVL] = controller
@ -181,12 +182,6 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
_LOGGER.debug("The envisalink sent a partition update event")
async_dispatcher_send(hass, SIGNAL_PARTITION_UPDATE, data)
@callback
def async_zone_bypass_update(data):
"""Handle zone bypass status updates."""
_LOGGER.debug("Envisalink sent a zone bypass update event. Updating zones")
async_dispatcher_send(hass, SIGNAL_ZONE_BYPASS_UPDATE, data)
@callback
def stop_envisalink(event):
"""Shutdown envisalink connection and thread on exit."""
@ -206,7 +201,6 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
controller.callback_login_failure = async_login_fail_callback
controller.callback_login_timeout = async_connection_fail_callback
controller.callback_login_success = async_connection_success_callback
controller.callback_zone_bypass_update = async_zone_bypass_update
_LOGGER.info("Start envisalink")
controller.start()
@ -240,13 +234,9 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
hass, Platform.BINARY_SENSOR, "envisalink", {CONF_ZONES: zones}, config
)
)
# Only DSC panels support getting zone bypass status
if panel_type == PANEL_TYPE_DSC:
hass.async_create_task(
async_load_platform(
hass, "switch", "envisalink", {CONF_ZONES: zones}, config
)
)
# Zone bypass switches are not currently created due to an issue with some panels.
# These switches will be re-added in the future after some further refactoring of the integration.
hass.services.async_register(
DOMAIN, SERVICE_CUSTOM_FUNCTION, handle_custom_function, schema=SERVICE_SCHEMA

View File

@ -2,7 +2,7 @@
"domain": "envisalink",
"name": "Envisalink",
"documentation": "https://www.home-assistant.io/integrations/envisalink",
"requirements": ["pyenvisalink==4.3"],
"codeowners": [],
"requirements": ["pyenvisalink==4.4"],
"codeowners": ["@ufodone"],
"iot_class": "local_push"
}

View File

@ -3,7 +3,7 @@
"name": "ESPHome",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/esphome",
"requirements": ["aioesphomeapi==10.8.1"],
"requirements": ["aioesphomeapi==10.8.2"],
"zeroconf": ["_esphomelib._tcp.local."],
"codeowners": ["@OttoWinter", "@jesserockz"],
"after_dependencies": ["zeroconf", "tag"],

View File

@ -57,6 +57,8 @@ KEY_POSITION = "position"
DEFAULT_NAME = "Cover Group"
# No limit on parallel updates to enable a group calling another group
PARALLEL_UPDATES = 0
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
{

View File

@ -52,6 +52,8 @@ SUPPORTED_FLAGS = {SUPPORT_SET_SPEED, SUPPORT_DIRECTION, SUPPORT_OSCILLATE}
DEFAULT_NAME = "Fan Group"
# No limit on parallel updates to enable a group calling another group
PARALLEL_UPDATES = 0
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
{

View File

@ -58,6 +58,9 @@ from .util import find_state_attributes, mean_tuple, reduce_attribute
DEFAULT_NAME = "Light Group"
# No limit on parallel updates to enable a group calling another group
PARALLEL_UPDATES = 0
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
{
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,

View File

@ -191,6 +191,8 @@ def parse_mapping(mapping, parents=None):
def setup(hass: HomeAssistant, base_config: ConfigType) -> bool: # noqa: C901
"""Set up the CEC capability."""
hass.data[DOMAIN] = {}
# Parse configuration into a dict of device name to physical address
# represented as a list of four elements.
device_aliases = {}

View File

@ -138,6 +138,11 @@ async def async_setup_entry(
devices = bridge.get_devices()
bridge_device = devices[BRIDGE_DEVICE_ID]
if not config_entry.unique_id:
hass.config_entries.async_update_entry(
config_entry, unique_id=hex(bridge_device["serial"])[2:].zfill(8)
)
buttons = bridge.buttons
_async_register_bridge_device(hass, entry_id, bridge_device)
button_devices = _async_register_button_devices(

View File

@ -3,7 +3,7 @@
"name": "Motion Blinds",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/motion_blinds",
"requirements": ["motionblinds==0.5.10"],
"requirements": ["motionblinds==0.5.12"],
"dependencies": ["network"],
"codeowners": ["@starkillerOG"],
"iot_class": "local_push"

View File

@ -4,7 +4,7 @@
"config_flow": true,
"dependencies": ["ffmpeg", "http", "media_source"],
"documentation": "https://www.home-assistant.io/integrations/nest",
"requirements": ["python-nest==4.1.0", "google-nest-sdm==1.6.0"],
"requirements": ["python-nest==4.2.0", "google-nest-sdm==1.7.1"],
"codeowners": ["@allenporter"],
"quality_scale": "platinum",
"dhcp": [

View File

@ -26,7 +26,7 @@ CONST_LIST_E_TO_H: list[str] = ["E", "F", "G", "H"]
CONST_LIST_I_TO_L: list[str] = ["I", "J", "K", "L"]
CONST_LIST_M_TO_Q: list[str] = ["M", "N", "O", "Ö", "P", "Q"]
CONST_LIST_R_TO_U: list[str] = ["R", "S", "T", "U", "Ü"]
CONST_LIST_V_TO_Z: list[str] = ["V", "W", "X", "Y"]
CONST_LIST_V_TO_Z: list[str] = ["V", "W", "X", "Y", "Z"]
CONST_REGION_A_TO_D: Final = "_a_to_d"
CONST_REGION_E_TO_H: Final = "_e_to_h"

View File

@ -82,7 +82,7 @@ async def async_setup_entry(
class PhilipsTVMediaPlayer(CoordinatorEntity, MediaPlayerEntity):
"""Representation of a Philips TV exposing the JointSpace API."""
_coordinator: PhilipsTVDataUpdateCoordinator
coordinator: PhilipsTVDataUpdateCoordinator
_attr_device_class = MediaPlayerDeviceClass.TV
def __init__(
@ -91,7 +91,6 @@ class PhilipsTVMediaPlayer(CoordinatorEntity, MediaPlayerEntity):
) -> None:
"""Initialize the Philips TV."""
self._tv = coordinator.api
self._coordinator = coordinator
self._sources = {}
self._channels = {}
self._supports = SUPPORT_PHILIPS_JS
@ -125,7 +124,7 @@ class PhilipsTVMediaPlayer(CoordinatorEntity, MediaPlayerEntity):
def supported_features(self):
"""Flag media player features that are supported."""
supports = self._supports
if self._coordinator.turn_on or (
if self.coordinator.turn_on or (
self._tv.on and self._tv.powerstate is not None
):
supports |= SUPPORT_TURN_ON
@ -170,7 +169,7 @@ class PhilipsTVMediaPlayer(CoordinatorEntity, MediaPlayerEntity):
await self._tv.setPowerState("On")
self._state = STATE_ON
else:
await self._coordinator.turn_on.async_run(self.hass, self._context)
await self.coordinator.turn_on.async_run(self.hass, self._context)
await self._async_update_soon()
async def async_turn_off(self):

View File

@ -30,7 +30,7 @@ async def async_setup_entry(
class PhilipsTVRemote(CoordinatorEntity, RemoteEntity):
"""Device that sends commands."""
_coordinator: PhilipsTVDataUpdateCoordinator
coordinator: PhilipsTVDataUpdateCoordinator
def __init__(
self,
@ -63,7 +63,7 @@ class PhilipsTVRemote(CoordinatorEntity, RemoteEntity):
if self._tv.on and self._tv.powerstate:
await self._tv.setPowerState("On")
else:
await self._coordinator.turn_on.async_run(self.hass, self._context)
await self.coordinator.turn_on.async_run(self.hass, self._context)
self.async_write_ha_state()
async def async_turn_off(self, **kwargs):

View File

@ -23,7 +23,7 @@ async def validate_input(hass: HomeAssistant, *, api_key: str, system_id: int) -
api_key=api_key,
system_id=system_id,
)
await pvoutput.status()
await pvoutput.system()
class PVOutputFlowHandler(ConfigFlow, domain=DOMAIN):

View File

@ -1,14 +1,14 @@
"""DataUpdateCoordinator for the PVOutput integration."""
from __future__ import annotations
from pvo import PVOutput, PVOutputAuthenticationError, Status
from pvo import PVOutput, PVOutputAuthenticationError, PVOutputNoDataError, Status
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_API_KEY
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .const import CONF_SYSTEM_ID, DOMAIN, LOGGER, SCAN_INTERVAL
@ -33,5 +33,7 @@ class PVOutputDataUpdateCoordinator(DataUpdateCoordinator[Status]):
"""Fetch system status from PVOutput."""
try:
return await self.pvoutput.status()
except PVOutputNoDataError as err:
raise UpdateFailed("PVOutput has no data available") from err
except PVOutputAuthenticationError as err:
raise ConfigEntryAuthFailed from err

View File

@ -4,7 +4,7 @@
"documentation": "https://www.home-assistant.io/integrations/pvoutput",
"config_flow": true,
"codeowners": ["@fabaff", "@frenck"],
"requirements": ["pvo==0.2.1"],
"requirements": ["pvo==0.2.2"],
"iot_class": "cloud_polling",
"quality_scale": "platinum"
}

View File

@ -40,7 +40,7 @@ def setup(hass: HomeAssistant, config: ConfigType) -> bool:
"https://github.com/home-assistant/architecture/blob/master/adr/0019-GPIO.md"
)
hass.data[DOMAIN][I2C_HATS_MANAGER] = I2CHatsManager()
hass.data[DOMAIN] = {I2C_HATS_MANAGER: I2CHatsManager()}
def start_i2c_hats_keep_alive(event):
"""Start I2C-HATs keep alive."""

View File

@ -44,6 +44,7 @@ SONOS_ALBUM_ARTIST = "album_artists"
SONOS_TRACKS = "tracks"
SONOS_COMPOSER = "composers"
SONOS_RADIO = "radio"
SONOS_OTHER_ITEM = "other items"
SONOS_STATE_PLAYING = "PLAYING"
SONOS_STATE_TRANSITIONING = "TRANSITIONING"
@ -76,6 +77,7 @@ SONOS_TO_MEDIA_CLASSES = {
"object.container.person.musicArtist": MEDIA_CLASS_ARTIST,
"object.container.playlistContainer.sameArtist": MEDIA_CLASS_ARTIST,
"object.container.playlistContainer": MEDIA_CLASS_PLAYLIST,
"object.item": MEDIA_CLASS_TRACK,
"object.item.audioItem.musicTrack": MEDIA_CLASS_TRACK,
"object.item.audioItem.audioBroadcast": MEDIA_CLASS_GENRE,
}
@ -121,6 +123,7 @@ SONOS_TYPES_MAPPING = {
"object.container.person.musicArtist": SONOS_ALBUM_ARTIST,
"object.container.playlistContainer.sameArtist": SONOS_ARTIST,
"object.container.playlistContainer": SONOS_PLAYLISTS,
"object.item": SONOS_OTHER_ITEM,
"object.item.audioItem.musicTrack": SONOS_TRACKS,
"object.item.audioItem.audioBroadcast": SONOS_RADIO,
}

View File

@ -162,8 +162,17 @@ def build_item_response(media_library, payload, get_thumbnail_url=None):
payload["idstring"].split("/")[2:]
)
try:
search_type = MEDIA_TYPES_TO_SONOS[payload["search_type"]]
except KeyError:
_LOGGER.debug(
"Unknown media type received when building item response: %s",
payload["search_type"],
)
return
media = media_library.browse_by_idstring(
MEDIA_TYPES_TO_SONOS[payload["search_type"]],
search_type,
payload["idstring"],
full_album_art_uri=True,
max_items=0,
@ -371,11 +380,16 @@ def favorites_payload(favorites):
group_types = {fav.reference.item_class for fav in favorites}
for group_type in sorted(group_types):
try:
media_content_type = SONOS_TYPES_MAPPING[group_type]
media_class = SONOS_TO_MEDIA_CLASSES[group_type]
except KeyError:
_LOGGER.debug("Unknown media type or class received %s", group_type)
continue
children.append(
BrowseMedia(
title=media_content_type.title(),
media_class=SONOS_TO_MEDIA_CLASSES[group_type],
media_class=media_class,
media_content_id=group_type,
media_content_type="favorites_folder",
can_play=False,

View File

@ -1,6 +1,11 @@
"""The spotify integration."""
from __future__ import annotations
from datetime import timedelta
from typing import Any
import aiohttp
import requests
from spotipy import Spotify, SpotifyException
import voluptuous as vol
@ -20,13 +25,16 @@ from homeassistant.helpers.config_entry_oauth2_flow import (
async_get_config_entry_implementation,
)
from homeassistant.helpers.typing import ConfigType
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from . import config_flow
from .const import (
DATA_SPOTIFY_CLIENT,
DATA_SPOTIFY_DEVICES,
DATA_SPOTIFY_ME,
DATA_SPOTIFY_SESSION,
DOMAIN,
LOGGER,
MEDIA_PLAYER_PREFIX,
SPOTIFY_SCOPES,
)
@ -112,9 +120,34 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
except SpotifyException as err:
raise ConfigEntryNotReady from err
async def _update_devices() -> list[dict[str, Any]]:
try:
devices: dict[str, Any] | None = await hass.async_add_executor_job(
spotify.devices
)
except (requests.RequestException, SpotifyException) as err:
raise UpdateFailed from err
if devices is None:
return []
return devices.get("devices", [])
device_coordinator: DataUpdateCoordinator[
list[dict[str, Any]]
] = DataUpdateCoordinator(
hass,
LOGGER,
name=f"{entry.title} Devices",
update_interval=timedelta(minutes=5),
update_method=_update_devices,
)
await device_coordinator.async_config_entry_first_refresh()
hass.data.setdefault(DOMAIN, {})
hass.data[DOMAIN][entry.entry_id] = {
DATA_SPOTIFY_CLIENT: spotify,
DATA_SPOTIFY_DEVICES: device_coordinator,
DATA_SPOTIFY_ME: current_user,
DATA_SPOTIFY_SESSION: session,
}

View File

@ -1,8 +1,13 @@
"""Define constants for the Spotify integration."""
import logging
DOMAIN = "spotify"
LOGGER = logging.getLogger(__package__)
DATA_SPOTIFY_CLIENT = "spotify_client"
DATA_SPOTIFY_DEVICES = "spotify_devices"
DATA_SPOTIFY_ME = "spotify_me"
DATA_SPOTIFY_SESSION = "spotify_session"

View File

@ -52,7 +52,7 @@ from homeassistant.const import (
STATE_PAUSED,
STATE_PLAYING,
)
from homeassistant.core import HomeAssistant
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.config_entry_oauth2_flow import OAuth2Session
from homeassistant.helpers.device_registry import DeviceEntryType
@ -62,6 +62,7 @@ from homeassistant.util.dt import utc_from_timestamp
from .const import (
DATA_SPOTIFY_CLIENT,
DATA_SPOTIFY_DEVICES,
DATA_SPOTIFY_ME,
DATA_SPOTIFY_SESSION,
DOMAIN,
@ -269,7 +270,6 @@ class SpotifyMediaPlayer(MediaPlayerEntity):
)
self._currently_playing: dict | None = {}
self._devices: list[dict] | None = []
self._playlist: dict | None = None
self._attr_name = self._name
@ -290,6 +290,11 @@ class SpotifyMediaPlayer(MediaPlayerEntity):
"""Return spotify API."""
return self._spotify_data[DATA_SPOTIFY_CLIENT]
@property
def _devices(self) -> list:
"""Return spotify devices."""
return self._spotify_data[DATA_SPOTIFY_DEVICES].data
@property
def device_info(self) -> DeviceInfo:
"""Return device information about this entity."""
@ -517,14 +522,14 @@ class SpotifyMediaPlayer(MediaPlayerEntity):
current = self._spotify.current_playback()
self._currently_playing = current or {}
self._playlist = None
context = self._currently_playing.get("context")
if context is not None and context["type"] == MEDIA_TYPE_PLAYLIST:
if context is not None and (
self._playlist is None or self._playlist["uri"] != context["uri"]
):
self._playlist = None
if context["type"] == MEDIA_TYPE_PLAYLIST:
self._playlist = self._spotify.playlist(current["context"]["uri"])
devices = self._spotify.devices() or {}
self._devices = devices.get("devices", [])
async def async_browse_media(self, media_content_type=None, media_content_id=None):
"""Implement the websocket media browsing helper."""
@ -543,6 +548,22 @@ class SpotifyMediaPlayer(MediaPlayerEntity):
media_content_id,
)
@callback
def _handle_devices_update(self) -> None:
"""Handle updated data from the coordinator."""
if not self.enabled:
return
self.async_write_ha_state()
async def async_added_to_hass(self) -> None:
"""When entity is added to hass."""
await super().async_added_to_hass()
self.async_on_remove(
self._spotify_data[DATA_SPOTIFY_DEVICES].async_add_listener(
self._handle_devices_update
)
)
async def async_browse_media_internal(
hass,

View File

@ -338,7 +338,6 @@ class Stream:
)
except StreamWorkerError as err:
self._logger.error("Error from stream worker: %s", str(err))
self._available = False
stream_state.discontinuity()
if not self.keepalive or self._thread_quit.is_set():

View File

@ -2,7 +2,7 @@
"domain": "synology_dsm",
"name": "Synology DSM",
"documentation": "https://www.home-assistant.io/integrations/synology_dsm",
"requirements": ["py-synologydsm-api==1.0.5"],
"requirements": ["py-synologydsm-api==1.0.6"],
"codeowners": ["@hacf-fr", "@Quentame", "@mib1185"],
"config_flow": true,
"ssdp": [

View File

@ -7,7 +7,7 @@ from .backports.enum import StrEnum
MAJOR_VERSION: Final = 2022
MINOR_VERSION: Final = 2
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)

View File

@ -223,7 +223,10 @@ def convert_to_entity_category(
"EntityCategory instead" % (type(value).__name__, value),
error_if_core=False,
)
try:
return EntityCategory(value)
except ValueError:
return None
return value

View File

@ -138,7 +138,7 @@ aio_georss_gdacs==0.5
aioambient==2021.11.0
# homeassistant.components.aseko_pool_live
aioaseko==0.0.1
aioaseko==0.0.2
# homeassistant.components.asuswrt
aioasuswrt==1.4.0
@ -166,7 +166,7 @@ aioeagle==1.1.0
aioemonitor==1.0.5
# homeassistant.components.esphome
aioesphomeapi==10.8.1
aioesphomeapi==10.8.2
# homeassistant.components.flo
aioflo==2021.11.0
@ -764,7 +764,7 @@ google-cloud-pubsub==2.9.0
google-cloud-texttospeech==0.4.0
# homeassistant.components.nest
google-nest-sdm==1.6.0
google-nest-sdm==1.7.1
# homeassistant.components.google_travel_time
googlemaps==2.5.1
@ -1049,7 +1049,7 @@ minio==5.0.10
mitemp_bt==0.0.5
# homeassistant.components.motion_blinds
motionblinds==0.5.10
motionblinds==0.5.12
# homeassistant.components.motioneye
motioneye-client==0.3.12
@ -1310,7 +1310,7 @@ pushbullet.py==0.11.0
pushover_complete==1.1.1
# homeassistant.components.pvoutput
pvo==0.2.1
pvo==0.2.2
# homeassistant.components.rpi_gpio_pwm
pwmled==1.6.7
@ -1331,7 +1331,7 @@ py-nightscout==1.2.2
py-schluter==0.1.7
# homeassistant.components.synology_dsm
py-synologydsm-api==1.0.5
py-synologydsm-api==1.0.6
# homeassistant.components.zabbix
py-zabbix==1.1.7
@ -1500,7 +1500,7 @@ pyeight==0.2.0
pyemby==1.8
# homeassistant.components.envisalink
pyenvisalink==4.3
pyenvisalink==4.4
# homeassistant.components.ephember
pyephember==0.3.1
@ -1954,7 +1954,7 @@ python-mpd2==3.0.4
python-mystrom==1.1.2
# homeassistant.components.nest
python-nest==4.1.0
python-nest==4.2.0
# homeassistant.components.ozw
python-openzwave-mqtt[mqtt-client]==1.4.0
@ -2513,7 +2513,7 @@ xs1-api-client==3.0.0
yalesmartalarmclient==0.3.7
# homeassistant.components.august
yalexs==1.1.20
yalexs==1.1.22
# homeassistant.components.yeelight
yeelight==0.7.8

View File

@ -91,7 +91,7 @@ aio_georss_gdacs==0.5
aioambient==2021.11.0
# homeassistant.components.aseko_pool_live
aioaseko==0.0.1
aioaseko==0.0.2
# homeassistant.components.asuswrt
aioasuswrt==1.4.0
@ -119,7 +119,7 @@ aioeagle==1.1.0
aioemonitor==1.0.5
# homeassistant.components.esphome
aioesphomeapi==10.8.1
aioesphomeapi==10.8.2
# homeassistant.components.flo
aioflo==2021.11.0
@ -492,7 +492,7 @@ google-api-python-client==1.6.4
google-cloud-pubsub==2.9.0
# homeassistant.components.nest
google-nest-sdm==1.6.0
google-nest-sdm==1.7.1
# homeassistant.components.google_travel_time
googlemaps==2.5.1
@ -655,7 +655,7 @@ millheater==0.9.0
minio==5.0.10
# homeassistant.components.motion_blinds
motionblinds==0.5.10
motionblinds==0.5.12
# homeassistant.components.motioneye
motioneye-client==0.3.12
@ -814,7 +814,7 @@ pure-python-adb[async]==0.3.0.dev0
pushbullet.py==0.11.0
# homeassistant.components.pvoutput
pvo==0.2.1
pvo==0.2.2
# homeassistant.components.canary
py-canary==0.5.1
@ -829,7 +829,7 @@ py-melissa-climate==2.1.4
py-nightscout==1.2.2
# homeassistant.components.synology_dsm
py-synologydsm-api==1.0.5
py-synologydsm-api==1.0.6
# homeassistant.components.seventeentrack
py17track==2021.12.2
@ -1206,7 +1206,7 @@ python-kasa==0.4.1
python-miio==0.5.9.2
# homeassistant.components.nest
python-nest==4.1.0
python-nest==4.2.0
# homeassistant.components.ozw
python-openzwave-mqtt[mqtt-client]==1.4.0
@ -1541,7 +1541,7 @@ xmltodict==0.12.0
yalesmartalarmclient==0.3.7
# homeassistant.components.august
yalexs==1.1.20
yalexs==1.1.22
# homeassistant.components.yeelight
yeelight==0.7.8

View File

@ -1,6 +1,6 @@
[metadata]
name = homeassistant
version = 2022.2.5
version = 2022.2.6
author = The Home Assistant Authors
author_email = hello@home-assistant.io
license = Apache-2.0

View File

@ -61,3 +61,25 @@ async def test_sensor(
assert state.attributes.get(ATTR_ARCH) == "aargh"
assert state.attributes.get(ATTR_BRAND) == "Intel Ryzen 7"
assert state.attributes.get(ATTR_HZ) == 3.6
async def test_sensor_partial_info(
hass: HomeAssistant,
mock_cpuinfo: MagicMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test the CPU Speed sensor missing info."""
mock_config_entry.add_to_hass(hass)
# Pop some info from the mocked CPUSpeed
mock_cpuinfo.return_value.pop("brand_raw")
mock_cpuinfo.return_value.pop("arch_string_raw")
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
state = hass.states.get("sensor.cpu_speed")
assert state
assert state.state == "3.2"
assert state.attributes.get(ATTR_ARCH) is None
assert state.attributes.get(ATTR_BRAND) is None

View File

@ -1,6 +1,7 @@
"""The tests for the group cover platform."""
from datetime import timedelta
import async_timeout
import pytest
from homeassistant.components.cover import (
@ -735,3 +736,52 @@ async def test_is_opening_closing(hass, setup_comp):
assert hass.states.get(DEMO_COVER_TILT).state == STATE_OPENING
assert hass.states.get(DEMO_COVER_POS).state == STATE_OPEN
assert hass.states.get(COVER_GROUP).state == STATE_OPENING
async def test_nested_group(hass):
"""Test nested cover group."""
await async_setup_component(
hass,
DOMAIN,
{
DOMAIN: [
{"platform": "demo"},
{
"platform": "group",
"entities": ["cover.bedroom_group"],
"name": "Nested Group",
},
{
"platform": "group",
CONF_ENTITIES: [DEMO_COVER_POS, DEMO_COVER_TILT],
"name": "Bedroom Group",
},
]
},
)
await hass.async_block_till_done()
await hass.async_start()
await hass.async_block_till_done()
state = hass.states.get("cover.bedroom_group")
assert state is not None
assert state.state == STATE_OPEN
assert state.attributes.get(ATTR_ENTITY_ID) == [DEMO_COVER_POS, DEMO_COVER_TILT]
state = hass.states.get("cover.nested_group")
assert state is not None
assert state.state == STATE_OPEN
assert state.attributes.get(ATTR_ENTITY_ID) == ["cover.bedroom_group"]
# Test controlling the nested group
async with async_timeout.timeout(0.5):
await hass.services.async_call(
DOMAIN,
SERVICE_CLOSE_COVER,
{ATTR_ENTITY_ID: "cover.nested_group"},
blocking=True,
)
assert hass.states.get(DEMO_COVER_POS).state == STATE_CLOSING
assert hass.states.get(DEMO_COVER_TILT).state == STATE_CLOSING
assert hass.states.get("cover.bedroom_group").state == STATE_CLOSING
assert hass.states.get("cover.nested_group").state == STATE_CLOSING

View File

@ -1,6 +1,7 @@
"""The tests for the group fan platform."""
from unittest.mock import patch
import async_timeout
import pytest
from homeassistant import config as hass_config
@ -497,3 +498,58 @@ async def test_service_calls(hass, setup_comp):
assert percentage_full_fan_state.attributes[ATTR_DIRECTION] == DIRECTION_REVERSE
fan_group_state = hass.states.get(FAN_GROUP)
assert fan_group_state.attributes[ATTR_DIRECTION] == DIRECTION_REVERSE
async def test_nested_group(hass):
"""Test nested fan group."""
await async_setup_component(
hass,
DOMAIN,
{
DOMAIN: [
{"platform": "demo"},
{
"platform": "group",
"entities": ["fan.bedroom_group"],
"name": "Nested Group",
},
{
"platform": "group",
CONF_ENTITIES: [
LIVING_ROOM_FAN_ENTITY_ID,
PERCENTAGE_FULL_FAN_ENTITY_ID,
],
"name": "Bedroom Group",
},
]
},
)
await hass.async_block_till_done()
await hass.async_start()
await hass.async_block_till_done()
state = hass.states.get("fan.bedroom_group")
assert state is not None
assert state.state == STATE_OFF
assert state.attributes.get(ATTR_ENTITY_ID) == [
LIVING_ROOM_FAN_ENTITY_ID,
PERCENTAGE_FULL_FAN_ENTITY_ID,
]
state = hass.states.get("fan.nested_group")
assert state is not None
assert state.state == STATE_OFF
assert state.attributes.get(ATTR_ENTITY_ID) == ["fan.bedroom_group"]
# Test controlling the nested group
async with async_timeout.timeout(0.5):
await hass.services.async_call(
DOMAIN,
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: "fan.nested_group"},
blocking=True,
)
assert hass.states.get(LIVING_ROOM_FAN_ENTITY_ID).state == STATE_ON
assert hass.states.get(PERCENTAGE_FULL_FAN_ENTITY_ID).state == STATE_ON
assert hass.states.get("fan.bedroom_group").state == STATE_ON
assert hass.states.get("fan.nested_group").state == STATE_ON

View File

@ -2,6 +2,7 @@
import unittest.mock
from unittest.mock import MagicMock, patch
import async_timeout
import pytest
from homeassistant import config as hass_config
@ -1470,12 +1471,12 @@ async def test_reload_with_base_integration_platform_not_setup(hass):
async def test_nested_group(hass):
"""Test nested light group."""
hass.states.async_set("light.kitchen", "on")
await async_setup_component(
hass,
LIGHT_DOMAIN,
{
LIGHT_DOMAIN: [
{"platform": "demo"},
{
"platform": DOMAIN,
"entities": ["light.bedroom_group"],
@ -1483,7 +1484,7 @@ async def test_nested_group(hass):
},
{
"platform": DOMAIN,
"entities": ["light.kitchen", "light.bedroom"],
"entities": ["light.bed_light", "light.kitchen_lights"],
"name": "Bedroom Group",
},
]
@ -1496,9 +1497,25 @@ async def test_nested_group(hass):
state = hass.states.get("light.bedroom_group")
assert state is not None
assert state.state == STATE_ON
assert state.attributes.get(ATTR_ENTITY_ID) == ["light.kitchen", "light.bedroom"]
assert state.attributes.get(ATTR_ENTITY_ID) == [
"light.bed_light",
"light.kitchen_lights",
]
state = hass.states.get("light.nested_group")
assert state is not None
assert state.state == STATE_ON
assert state.attributes.get(ATTR_ENTITY_ID) == ["light.bedroom_group"]
# Test controlling the nested group
async with async_timeout.timeout(0.5):
await hass.services.async_call(
LIGHT_DOMAIN,
SERVICE_TOGGLE,
{ATTR_ENTITY_ID: "light.nested_group"},
blocking=True,
)
assert hass.states.get("light.bed_light").state == STATE_OFF
assert hass.states.get("light.kitchen_lights").state == STATE_OFF
assert hass.states.get("light.bedroom_group").state == STATE_OFF
assert hass.states.get("light.nested_group").state == STATE_OFF

View File

@ -1,6 +1,7 @@
"""The tests for the Media group platform."""
from unittest.mock import patch
import async_timeout
import pytest
from homeassistant.components.group import DOMAIN
@ -486,12 +487,12 @@ async def test_service_calls(hass, mock_media_seek):
async def test_nested_group(hass):
"""Test nested media group."""
hass.states.async_set("media_player.player_1", "on")
await async_setup_component(
hass,
MEDIA_DOMAIN,
{
MEDIA_DOMAIN: [
{"platform": "demo"},
{
"platform": DOMAIN,
"entities": ["media_player.group_1"],
@ -499,7 +500,7 @@ async def test_nested_group(hass):
},
{
"platform": DOMAIN,
"entities": ["media_player.player_1", "media_player.player_2"],
"entities": ["media_player.bedroom", "media_player.kitchen"],
"name": "Group 1",
},
]
@ -511,13 +512,28 @@ async def test_nested_group(hass):
state = hass.states.get("media_player.group_1")
assert state is not None
assert state.state == STATE_ON
assert state.state == STATE_PLAYING
assert state.attributes.get(ATTR_ENTITY_ID) == [
"media_player.player_1",
"media_player.player_2",
"media_player.bedroom",
"media_player.kitchen",
]
state = hass.states.get("media_player.nested_group")
assert state is not None
assert state.state == STATE_ON
assert state.state == STATE_PLAYING
assert state.attributes.get(ATTR_ENTITY_ID) == ["media_player.group_1"]
# Test controlling the nested group
async with async_timeout.timeout(0.5):
await hass.services.async_call(
MEDIA_DOMAIN,
SERVICE_TURN_OFF,
{ATTR_ENTITY_ID: "media_player.group_1"},
blocking=True,
)
await hass.async_block_till_done()
assert hass.states.get("media_player.bedroom").state == STATE_OFF
assert hass.states.get("media_player.kitchen").state == STATE_OFF
assert hass.states.get("media_player.group_1").state == STATE_OFF
assert hass.states.get("media_player.nested_group").state == STATE_OFF

View File

@ -47,7 +47,7 @@ async def test_full_user_flow(
}
assert len(mock_setup_entry.mock_calls) == 1
assert len(mock_pvoutput_config_flow.status.mock_calls) == 1
assert len(mock_pvoutput_config_flow.system.mock_calls) == 1
async def test_full_flow_with_authentication_error(
@ -68,7 +68,7 @@ async def test_full_flow_with_authentication_error(
assert result.get("step_id") == SOURCE_USER
assert "flow_id" in result
mock_pvoutput_config_flow.status.side_effect = PVOutputAuthenticationError
mock_pvoutput_config_flow.system.side_effect = PVOutputAuthenticationError
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={
@ -83,9 +83,9 @@ async def test_full_flow_with_authentication_error(
assert "flow_id" in result2
assert len(mock_setup_entry.mock_calls) == 0
assert len(mock_pvoutput_config_flow.status.mock_calls) == 1
assert len(mock_pvoutput_config_flow.system.mock_calls) == 1
mock_pvoutput_config_flow.status.side_effect = None
mock_pvoutput_config_flow.system.side_effect = None
result3 = await hass.config_entries.flow.async_configure(
result2["flow_id"],
user_input={
@ -102,14 +102,14 @@ async def test_full_flow_with_authentication_error(
}
assert len(mock_setup_entry.mock_calls) == 1
assert len(mock_pvoutput_config_flow.status.mock_calls) == 2
assert len(mock_pvoutput_config_flow.system.mock_calls) == 2
async def test_connection_error(
hass: HomeAssistant, mock_pvoutput_config_flow: MagicMock
) -> None:
"""Test API connection error."""
mock_pvoutput_config_flow.status.side_effect = PVOutputConnectionError
mock_pvoutput_config_flow.system.side_effect = PVOutputConnectionError
result = await hass.config_entries.flow.async_init(
DOMAIN,
@ -123,7 +123,7 @@ async def test_connection_error(
assert result.get("type") == RESULT_TYPE_FORM
assert result.get("errors") == {"base": "cannot_connect"}
assert len(mock_pvoutput_config_flow.status.mock_calls) == 1
assert len(mock_pvoutput_config_flow.system.mock_calls) == 1
async def test_already_configured(
@ -175,7 +175,7 @@ async def test_import_flow(
}
assert len(mock_setup_entry.mock_calls) == 1
assert len(mock_pvoutput_config_flow.status.mock_calls) == 1
assert len(mock_pvoutput_config_flow.system.mock_calls) == 1
async def test_reauth_flow(
@ -214,7 +214,7 @@ async def test_reauth_flow(
}
assert len(mock_setup_entry.mock_calls) == 1
assert len(mock_pvoutput_config_flow.status.mock_calls) == 1
assert len(mock_pvoutput_config_flow.system.mock_calls) == 1
async def test_reauth_with_authentication_error(
@ -243,7 +243,7 @@ async def test_reauth_with_authentication_error(
assert result.get("step_id") == "reauth_confirm"
assert "flow_id" in result
mock_pvoutput_config_flow.status.side_effect = PVOutputAuthenticationError
mock_pvoutput_config_flow.system.side_effect = PVOutputAuthenticationError
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{CONF_API_KEY: "invalid_key"},
@ -256,9 +256,9 @@ async def test_reauth_with_authentication_error(
assert "flow_id" in result2
assert len(mock_setup_entry.mock_calls) == 0
assert len(mock_pvoutput_config_flow.status.mock_calls) == 1
assert len(mock_pvoutput_config_flow.system.mock_calls) == 1
mock_pvoutput_config_flow.status.side_effect = None
mock_pvoutput_config_flow.system.side_effect = None
result3 = await hass.config_entries.flow.async_configure(
result2["flow_id"],
user_input={CONF_API_KEY: "valid_key"},
@ -273,7 +273,7 @@ async def test_reauth_with_authentication_error(
}
assert len(mock_setup_entry.mock_calls) == 1
assert len(mock_pvoutput_config_flow.status.mock_calls) == 2
assert len(mock_pvoutput_config_flow.system.mock_calls) == 2
async def test_reauth_api_error(
@ -297,7 +297,7 @@ async def test_reauth_api_error(
assert result.get("step_id") == "reauth_confirm"
assert "flow_id" in result
mock_pvoutput_config_flow.status.side_effect = PVOutputConnectionError
mock_pvoutput_config_flow.system.side_effect = PVOutputConnectionError
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{CONF_API_KEY: "some_new_key"},

View File

@ -1,7 +1,11 @@
"""Tests for the PVOutput integration."""
from unittest.mock import MagicMock
from pvo import PVOutputAuthenticationError, PVOutputConnectionError
from pvo import (
PVOutputAuthenticationError,
PVOutputConnectionError,
PVOutputNoDataError,
)
import pytest
from homeassistant.components.pvoutput.const import CONF_SYSTEM_ID, DOMAIN
@ -35,13 +39,15 @@ async def test_load_unload_config_entry(
assert mock_config_entry.state is ConfigEntryState.NOT_LOADED
@pytest.mark.parametrize("side_effect", [PVOutputConnectionError, PVOutputNoDataError])
async def test_config_entry_not_ready(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_pvoutput: MagicMock,
side_effect: Exception,
) -> None:
"""Test the PVOutput configuration entry not ready."""
mock_pvoutput.status.side_effect = PVOutputConnectionError
mock_pvoutput.status.side_effect = side_effect
mock_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(mock_config_entry.entry_id)

View File

@ -1124,3 +1124,15 @@ async def test_deprecated_disabled_by_str(hass, registry, caplog):
assert entry.disabled_by is er.RegistryEntryDisabler.USER
assert " str for entity registry disabled_by. This is deprecated " in caplog.text
async def test_invalid_entity_category_str(hass, registry, caplog):
"""Test use of invalid entity category."""
entry = er.RegistryEntry(
"light",
"hue",
"5678",
entity_category="invalid",
)
assert entry.entity_category is None