mirror of
https://github.com/home-assistant/core.git
synced 2025-07-15 01:07:10 +00:00
Merge pull request #66351 from home-assistant/rc
This commit is contained in:
commit
fc2d30c993
@ -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
|
||||
|
@ -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"
|
||||
],
|
||||
|
@ -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:
|
||||
|
@ -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": [
|
||||
{
|
||||
|
@ -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:
|
||||
stream = 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")
|
||||
if stream is None:
|
||||
raise web.HTTPBadGateway()
|
||||
return stream
|
||||
|
@ -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(
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
}
|
||||
|
@ -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"],
|
||||
|
@ -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(
|
||||
{
|
||||
|
@ -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(
|
||||
{
|
||||
|
@ -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,
|
||||
|
@ -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 = {}
|
||||
|
@ -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(
|
||||
|
@ -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"
|
||||
|
@ -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": [
|
||||
|
@ -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"
|
||||
|
@ -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):
|
||||
|
@ -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):
|
||||
|
@ -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):
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
}
|
||||
|
@ -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."""
|
||||
|
@ -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,
|
||||
}
|
||||
|
@ -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):
|
||||
media_content_type = SONOS_TYPES_MAPPING[group_type]
|
||||
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,
|
||||
|
@ -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,
|
||||
}
|
||||
|
@ -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"
|
||||
|
||||
|
@ -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,13 +522,13 @@ 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:
|
||||
self._playlist = self._spotify.playlist(current["context"]["uri"])
|
||||
|
||||
devices = self._spotify.devices() or {}
|
||||
self._devices = devices.get("devices", [])
|
||||
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"])
|
||||
|
||||
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,
|
||||
|
@ -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():
|
||||
|
@ -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": [
|
||||
|
@ -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)
|
||||
|
@ -223,7 +223,10 @@ def convert_to_entity_category(
|
||||
"EntityCategory instead" % (type(value).__name__, value),
|
||||
error_if_core=False,
|
||||
)
|
||||
return EntityCategory(value)
|
||||
try:
|
||||
return EntityCategory(value)
|
||||
except ValueError:
|
||||
return None
|
||||
return value
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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"},
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user