mirror of
https://github.com/home-assistant/core.git
synced 2025-07-15 17:27: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/entur_public_transport/* @hfurubotten
|
||||||
homeassistant/components/environment_canada/* @gwww @michaeldavie
|
homeassistant/components/environment_canada/* @gwww @michaeldavie
|
||||||
tests/components/environment_canada/* @gwww @michaeldavie
|
tests/components/environment_canada/* @gwww @michaeldavie
|
||||||
|
homeassistant/components/envisalink/* @ufodone
|
||||||
homeassistant/components/ephember/* @ttroy50
|
homeassistant/components/ephember/* @ttroy50
|
||||||
homeassistant/components/epson/* @pszafer
|
homeassistant/components/epson/* @pszafer
|
||||||
tests/components/epson/* @pszafer
|
tests/components/epson/* @pszafer
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"name": "Aseko Pool Live",
|
"name": "Aseko Pool Live",
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/aseko_pool_live",
|
"documentation": "https://www.home-assistant.io/integrations/aseko_pool_live",
|
||||||
"requirements": ["aioaseko==0.0.1"],
|
"requirements": ["aioaseko==0.0.2"],
|
||||||
"codeowners": [
|
"codeowners": [
|
||||||
"@milanmeu"
|
"@milanmeu"
|
||||||
],
|
],
|
||||||
|
@ -75,6 +75,7 @@ async def async_setup_august(
|
|||||||
hass.config_entries.async_update_entry(config_entry, data=config_data)
|
hass.config_entries.async_update_entry(config_entry, data=config_data)
|
||||||
|
|
||||||
await august_gateway.async_authenticate()
|
await august_gateway.async_authenticate()
|
||||||
|
await august_gateway.async_refresh_access_token_if_needed()
|
||||||
|
|
||||||
hass.data.setdefault(DOMAIN, {})
|
hass.data.setdefault(DOMAIN, {})
|
||||||
data = hass.data[DOMAIN][config_entry.entry_id] = {
|
data = hass.data[DOMAIN][config_entry.entry_id] = {
|
||||||
@ -106,11 +107,10 @@ class AugustData(AugustSubscriberMixin):
|
|||||||
async def async_setup(self):
|
async def async_setup(self):
|
||||||
"""Async setup of august device data and activities."""
|
"""Async setup of august device data and activities."""
|
||||||
token = self._august_gateway.access_token
|
token = self._august_gateway.access_token
|
||||||
user_data, locks, doorbells = await asyncio.gather(
|
# This used to be a gather but it was less reliable with august's recent api changes.
|
||||||
self._api.async_get_user(token),
|
user_data = await self._api.async_get_user(token)
|
||||||
self._api.async_get_operable_locks(token),
|
locks = await self._api.async_get_operable_locks(token)
|
||||||
self._api.async_get_doorbells(token),
|
doorbells = await self._api.async_get_doorbells(token)
|
||||||
)
|
|
||||||
if not doorbells:
|
if not doorbells:
|
||||||
doorbells = []
|
doorbells = []
|
||||||
if not locks:
|
if not locks:
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
"domain": "august",
|
"domain": "august",
|
||||||
"name": "August",
|
"name": "August",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/august",
|
"documentation": "https://www.home-assistant.io/integrations/august",
|
||||||
"requirements": ["yalexs==1.1.20"],
|
"requirements": ["yalexs==1.1.22"],
|
||||||
"codeowners": ["@bdraco"],
|
"codeowners": ["@bdraco"],
|
||||||
"dhcp": [
|
"dhcp": [
|
||||||
{
|
{
|
||||||
|
@ -222,7 +222,12 @@ async def async_get_mjpeg_stream(
|
|||||||
"""Fetch an mjpeg stream from a camera entity."""
|
"""Fetch an mjpeg stream from a camera entity."""
|
||||||
camera = _get_camera_from_entity_id(hass, entity_id)
|
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(
|
async def async_get_still_stream(
|
||||||
@ -784,7 +789,11 @@ class CameraMjpegStream(CameraView):
|
|||||||
async def handle(self, request: web.Request, camera: Camera) -> web.StreamResponse:
|
async def handle(self, request: web.Request, camera: Camera) -> web.StreamResponse:
|
||||||
"""Serve camera stream, possibly with interval."""
|
"""Serve camera stream, possibly with interval."""
|
||||||
if (interval_str := request.query.get("interval")) is None:
|
if (interval_str := request.query.get("interval")) is None:
|
||||||
|
try:
|
||||||
stream = await camera.handle_async_mjpeg_stream(request)
|
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:
|
if stream is None:
|
||||||
raise web.HTTPBadGateway()
|
raise web.HTTPBadGateway()
|
||||||
return stream
|
return stream
|
||||||
|
@ -81,8 +81,8 @@ class CPUSpeedSensor(SensorEntity):
|
|||||||
|
|
||||||
if info:
|
if info:
|
||||||
self._attr_extra_state_attributes = {
|
self._attr_extra_state_attributes = {
|
||||||
ATTR_ARCH: info["arch_string_raw"],
|
ATTR_ARCH: info.get("arch_string_raw"),
|
||||||
ATTR_BRAND: info["brand_raw"],
|
ATTR_BRAND: info.get("brand_raw"),
|
||||||
}
|
}
|
||||||
if HZ_ADVERTISED in info:
|
if HZ_ADVERTISED in info:
|
||||||
self._attr_extra_state_attributes[ATTR_HZ] = round(
|
self._attr_extra_state_attributes[ATTR_HZ] = round(
|
||||||
|
@ -137,6 +137,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
|||||||
keep_alive,
|
keep_alive,
|
||||||
hass.loop,
|
hass.loop,
|
||||||
connection_timeout,
|
connection_timeout,
|
||||||
|
False,
|
||||||
)
|
)
|
||||||
hass.data[DATA_EVL] = controller
|
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")
|
_LOGGER.debug("The envisalink sent a partition update event")
|
||||||
async_dispatcher_send(hass, SIGNAL_PARTITION_UPDATE, data)
|
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
|
@callback
|
||||||
def stop_envisalink(event):
|
def stop_envisalink(event):
|
||||||
"""Shutdown envisalink connection and thread on exit."""
|
"""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_failure = async_login_fail_callback
|
||||||
controller.callback_login_timeout = async_connection_fail_callback
|
controller.callback_login_timeout = async_connection_fail_callback
|
||||||
controller.callback_login_success = async_connection_success_callback
|
controller.callback_login_success = async_connection_success_callback
|
||||||
controller.callback_zone_bypass_update = async_zone_bypass_update
|
|
||||||
|
|
||||||
_LOGGER.info("Start envisalink")
|
_LOGGER.info("Start envisalink")
|
||||||
controller.start()
|
controller.start()
|
||||||
@ -240,13 +234,9 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
|||||||
hass, Platform.BINARY_SENSOR, "envisalink", {CONF_ZONES: zones}, config
|
hass, Platform.BINARY_SENSOR, "envisalink", {CONF_ZONES: zones}, config
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
# Only DSC panels support getting zone bypass status
|
|
||||||
if panel_type == PANEL_TYPE_DSC:
|
# Zone bypass switches are not currently created due to an issue with some panels.
|
||||||
hass.async_create_task(
|
# These switches will be re-added in the future after some further refactoring of the integration.
|
||||||
async_load_platform(
|
|
||||||
hass, "switch", "envisalink", {CONF_ZONES: zones}, config
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
hass.services.async_register(
|
hass.services.async_register(
|
||||||
DOMAIN, SERVICE_CUSTOM_FUNCTION, handle_custom_function, schema=SERVICE_SCHEMA
|
DOMAIN, SERVICE_CUSTOM_FUNCTION, handle_custom_function, schema=SERVICE_SCHEMA
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
"domain": "envisalink",
|
"domain": "envisalink",
|
||||||
"name": "Envisalink",
|
"name": "Envisalink",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/envisalink",
|
"documentation": "https://www.home-assistant.io/integrations/envisalink",
|
||||||
"requirements": ["pyenvisalink==4.3"],
|
"requirements": ["pyenvisalink==4.4"],
|
||||||
"codeowners": [],
|
"codeowners": ["@ufodone"],
|
||||||
"iot_class": "local_push"
|
"iot_class": "local_push"
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"name": "ESPHome",
|
"name": "ESPHome",
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/esphome",
|
"documentation": "https://www.home-assistant.io/integrations/esphome",
|
||||||
"requirements": ["aioesphomeapi==10.8.1"],
|
"requirements": ["aioesphomeapi==10.8.2"],
|
||||||
"zeroconf": ["_esphomelib._tcp.local."],
|
"zeroconf": ["_esphomelib._tcp.local."],
|
||||||
"codeowners": ["@OttoWinter", "@jesserockz"],
|
"codeowners": ["@OttoWinter", "@jesserockz"],
|
||||||
"after_dependencies": ["zeroconf", "tag"],
|
"after_dependencies": ["zeroconf", "tag"],
|
||||||
|
@ -57,6 +57,8 @@ KEY_POSITION = "position"
|
|||||||
|
|
||||||
DEFAULT_NAME = "Cover Group"
|
DEFAULT_NAME = "Cover Group"
|
||||||
|
|
||||||
|
# No limit on parallel updates to enable a group calling another group
|
||||||
|
PARALLEL_UPDATES = 0
|
||||||
|
|
||||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
||||||
{
|
{
|
||||||
|
@ -52,6 +52,8 @@ SUPPORTED_FLAGS = {SUPPORT_SET_SPEED, SUPPORT_DIRECTION, SUPPORT_OSCILLATE}
|
|||||||
|
|
||||||
DEFAULT_NAME = "Fan Group"
|
DEFAULT_NAME = "Fan Group"
|
||||||
|
|
||||||
|
# No limit on parallel updates to enable a group calling another group
|
||||||
|
PARALLEL_UPDATES = 0
|
||||||
|
|
||||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
||||||
{
|
{
|
||||||
|
@ -58,6 +58,9 @@ from .util import find_state_attributes, mean_tuple, reduce_attribute
|
|||||||
|
|
||||||
DEFAULT_NAME = "Light Group"
|
DEFAULT_NAME = "Light Group"
|
||||||
|
|
||||||
|
# No limit on parallel updates to enable a group calling another group
|
||||||
|
PARALLEL_UPDATES = 0
|
||||||
|
|
||||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
||||||
{
|
{
|
||||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
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
|
def setup(hass: HomeAssistant, base_config: ConfigType) -> bool: # noqa: C901
|
||||||
"""Set up the CEC capability."""
|
"""Set up the CEC capability."""
|
||||||
|
|
||||||
|
hass.data[DOMAIN] = {}
|
||||||
|
|
||||||
# Parse configuration into a dict of device name to physical address
|
# Parse configuration into a dict of device name to physical address
|
||||||
# represented as a list of four elements.
|
# represented as a list of four elements.
|
||||||
device_aliases = {}
|
device_aliases = {}
|
||||||
|
@ -138,6 +138,11 @@ async def async_setup_entry(
|
|||||||
|
|
||||||
devices = bridge.get_devices()
|
devices = bridge.get_devices()
|
||||||
bridge_device = devices[BRIDGE_DEVICE_ID]
|
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
|
buttons = bridge.buttons
|
||||||
_async_register_bridge_device(hass, entry_id, bridge_device)
|
_async_register_bridge_device(hass, entry_id, bridge_device)
|
||||||
button_devices = _async_register_button_devices(
|
button_devices = _async_register_button_devices(
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"name": "Motion Blinds",
|
"name": "Motion Blinds",
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/motion_blinds",
|
"documentation": "https://www.home-assistant.io/integrations/motion_blinds",
|
||||||
"requirements": ["motionblinds==0.5.10"],
|
"requirements": ["motionblinds==0.5.12"],
|
||||||
"dependencies": ["network"],
|
"dependencies": ["network"],
|
||||||
"codeowners": ["@starkillerOG"],
|
"codeowners": ["@starkillerOG"],
|
||||||
"iot_class": "local_push"
|
"iot_class": "local_push"
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"dependencies": ["ffmpeg", "http", "media_source"],
|
"dependencies": ["ffmpeg", "http", "media_source"],
|
||||||
"documentation": "https://www.home-assistant.io/integrations/nest",
|
"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"],
|
"codeowners": ["@allenporter"],
|
||||||
"quality_scale": "platinum",
|
"quality_scale": "platinum",
|
||||||
"dhcp": [
|
"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_I_TO_L: list[str] = ["I", "J", "K", "L"]
|
||||||
CONST_LIST_M_TO_Q: list[str] = ["M", "N", "O", "Ö", "P", "Q"]
|
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_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_A_TO_D: Final = "_a_to_d"
|
||||||
CONST_REGION_E_TO_H: Final = "_e_to_h"
|
CONST_REGION_E_TO_H: Final = "_e_to_h"
|
||||||
|
@ -82,7 +82,7 @@ async def async_setup_entry(
|
|||||||
class PhilipsTVMediaPlayer(CoordinatorEntity, MediaPlayerEntity):
|
class PhilipsTVMediaPlayer(CoordinatorEntity, MediaPlayerEntity):
|
||||||
"""Representation of a Philips TV exposing the JointSpace API."""
|
"""Representation of a Philips TV exposing the JointSpace API."""
|
||||||
|
|
||||||
_coordinator: PhilipsTVDataUpdateCoordinator
|
coordinator: PhilipsTVDataUpdateCoordinator
|
||||||
_attr_device_class = MediaPlayerDeviceClass.TV
|
_attr_device_class = MediaPlayerDeviceClass.TV
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
@ -91,7 +91,6 @@ class PhilipsTVMediaPlayer(CoordinatorEntity, MediaPlayerEntity):
|
|||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize the Philips TV."""
|
"""Initialize the Philips TV."""
|
||||||
self._tv = coordinator.api
|
self._tv = coordinator.api
|
||||||
self._coordinator = coordinator
|
|
||||||
self._sources = {}
|
self._sources = {}
|
||||||
self._channels = {}
|
self._channels = {}
|
||||||
self._supports = SUPPORT_PHILIPS_JS
|
self._supports = SUPPORT_PHILIPS_JS
|
||||||
@ -125,7 +124,7 @@ class PhilipsTVMediaPlayer(CoordinatorEntity, MediaPlayerEntity):
|
|||||||
def supported_features(self):
|
def supported_features(self):
|
||||||
"""Flag media player features that are supported."""
|
"""Flag media player features that are supported."""
|
||||||
supports = self._supports
|
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
|
self._tv.on and self._tv.powerstate is not None
|
||||||
):
|
):
|
||||||
supports |= SUPPORT_TURN_ON
|
supports |= SUPPORT_TURN_ON
|
||||||
@ -170,7 +169,7 @@ class PhilipsTVMediaPlayer(CoordinatorEntity, MediaPlayerEntity):
|
|||||||
await self._tv.setPowerState("On")
|
await self._tv.setPowerState("On")
|
||||||
self._state = STATE_ON
|
self._state = STATE_ON
|
||||||
else:
|
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()
|
await self._async_update_soon()
|
||||||
|
|
||||||
async def async_turn_off(self):
|
async def async_turn_off(self):
|
||||||
|
@ -30,7 +30,7 @@ async def async_setup_entry(
|
|||||||
class PhilipsTVRemote(CoordinatorEntity, RemoteEntity):
|
class PhilipsTVRemote(CoordinatorEntity, RemoteEntity):
|
||||||
"""Device that sends commands."""
|
"""Device that sends commands."""
|
||||||
|
|
||||||
_coordinator: PhilipsTVDataUpdateCoordinator
|
coordinator: PhilipsTVDataUpdateCoordinator
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@ -63,7 +63,7 @@ class PhilipsTVRemote(CoordinatorEntity, RemoteEntity):
|
|||||||
if self._tv.on and self._tv.powerstate:
|
if self._tv.on and self._tv.powerstate:
|
||||||
await self._tv.setPowerState("On")
|
await self._tv.setPowerState("On")
|
||||||
else:
|
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()
|
self.async_write_ha_state()
|
||||||
|
|
||||||
async def async_turn_off(self, **kwargs):
|
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,
|
api_key=api_key,
|
||||||
system_id=system_id,
|
system_id=system_id,
|
||||||
)
|
)
|
||||||
await pvoutput.status()
|
await pvoutput.system()
|
||||||
|
|
||||||
|
|
||||||
class PVOutputFlowHandler(ConfigFlow, domain=DOMAIN):
|
class PVOutputFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
"""DataUpdateCoordinator for the PVOutput integration."""
|
"""DataUpdateCoordinator for the PVOutput integration."""
|
||||||
from __future__ import annotations
|
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.config_entries import ConfigEntry
|
||||||
from homeassistant.const import CONF_API_KEY
|
from homeassistant.const import CONF_API_KEY
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.exceptions import ConfigEntryAuthFailed
|
from homeassistant.exceptions import ConfigEntryAuthFailed
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
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
|
from .const import CONF_SYSTEM_ID, DOMAIN, LOGGER, SCAN_INTERVAL
|
||||||
|
|
||||||
@ -33,5 +33,7 @@ class PVOutputDataUpdateCoordinator(DataUpdateCoordinator[Status]):
|
|||||||
"""Fetch system status from PVOutput."""
|
"""Fetch system status from PVOutput."""
|
||||||
try:
|
try:
|
||||||
return await self.pvoutput.status()
|
return await self.pvoutput.status()
|
||||||
|
except PVOutputNoDataError as err:
|
||||||
|
raise UpdateFailed("PVOutput has no data available") from err
|
||||||
except PVOutputAuthenticationError as err:
|
except PVOutputAuthenticationError as err:
|
||||||
raise ConfigEntryAuthFailed from err
|
raise ConfigEntryAuthFailed from err
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/pvoutput",
|
"documentation": "https://www.home-assistant.io/integrations/pvoutput",
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"codeowners": ["@fabaff", "@frenck"],
|
"codeowners": ["@fabaff", "@frenck"],
|
||||||
"requirements": ["pvo==0.2.1"],
|
"requirements": ["pvo==0.2.2"],
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"quality_scale": "platinum"
|
"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"
|
"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):
|
def start_i2c_hats_keep_alive(event):
|
||||||
"""Start I2C-HATs keep alive."""
|
"""Start I2C-HATs keep alive."""
|
||||||
|
@ -44,6 +44,7 @@ SONOS_ALBUM_ARTIST = "album_artists"
|
|||||||
SONOS_TRACKS = "tracks"
|
SONOS_TRACKS = "tracks"
|
||||||
SONOS_COMPOSER = "composers"
|
SONOS_COMPOSER = "composers"
|
||||||
SONOS_RADIO = "radio"
|
SONOS_RADIO = "radio"
|
||||||
|
SONOS_OTHER_ITEM = "other items"
|
||||||
|
|
||||||
SONOS_STATE_PLAYING = "PLAYING"
|
SONOS_STATE_PLAYING = "PLAYING"
|
||||||
SONOS_STATE_TRANSITIONING = "TRANSITIONING"
|
SONOS_STATE_TRANSITIONING = "TRANSITIONING"
|
||||||
@ -76,6 +77,7 @@ SONOS_TO_MEDIA_CLASSES = {
|
|||||||
"object.container.person.musicArtist": MEDIA_CLASS_ARTIST,
|
"object.container.person.musicArtist": MEDIA_CLASS_ARTIST,
|
||||||
"object.container.playlistContainer.sameArtist": MEDIA_CLASS_ARTIST,
|
"object.container.playlistContainer.sameArtist": MEDIA_CLASS_ARTIST,
|
||||||
"object.container.playlistContainer": MEDIA_CLASS_PLAYLIST,
|
"object.container.playlistContainer": MEDIA_CLASS_PLAYLIST,
|
||||||
|
"object.item": MEDIA_CLASS_TRACK,
|
||||||
"object.item.audioItem.musicTrack": MEDIA_CLASS_TRACK,
|
"object.item.audioItem.musicTrack": MEDIA_CLASS_TRACK,
|
||||||
"object.item.audioItem.audioBroadcast": MEDIA_CLASS_GENRE,
|
"object.item.audioItem.audioBroadcast": MEDIA_CLASS_GENRE,
|
||||||
}
|
}
|
||||||
@ -121,6 +123,7 @@ SONOS_TYPES_MAPPING = {
|
|||||||
"object.container.person.musicArtist": SONOS_ALBUM_ARTIST,
|
"object.container.person.musicArtist": SONOS_ALBUM_ARTIST,
|
||||||
"object.container.playlistContainer.sameArtist": SONOS_ARTIST,
|
"object.container.playlistContainer.sameArtist": SONOS_ARTIST,
|
||||||
"object.container.playlistContainer": SONOS_PLAYLISTS,
|
"object.container.playlistContainer": SONOS_PLAYLISTS,
|
||||||
|
"object.item": SONOS_OTHER_ITEM,
|
||||||
"object.item.audioItem.musicTrack": SONOS_TRACKS,
|
"object.item.audioItem.musicTrack": SONOS_TRACKS,
|
||||||
"object.item.audioItem.audioBroadcast": SONOS_RADIO,
|
"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:]
|
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 = media_library.browse_by_idstring(
|
||||||
MEDIA_TYPES_TO_SONOS[payload["search_type"]],
|
search_type,
|
||||||
payload["idstring"],
|
payload["idstring"],
|
||||||
full_album_art_uri=True,
|
full_album_art_uri=True,
|
||||||
max_items=0,
|
max_items=0,
|
||||||
@ -371,11 +380,16 @@ def favorites_payload(favorites):
|
|||||||
|
|
||||||
group_types = {fav.reference.item_class for fav in favorites}
|
group_types = {fav.reference.item_class for fav in favorites}
|
||||||
for group_type in sorted(group_types):
|
for group_type in sorted(group_types):
|
||||||
|
try:
|
||||||
media_content_type = SONOS_TYPES_MAPPING[group_type]
|
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(
|
children.append(
|
||||||
BrowseMedia(
|
BrowseMedia(
|
||||||
title=media_content_type.title(),
|
title=media_content_type.title(),
|
||||||
media_class=SONOS_TO_MEDIA_CLASSES[group_type],
|
media_class=media_class,
|
||||||
media_content_id=group_type,
|
media_content_id=group_type,
|
||||||
media_content_type="favorites_folder",
|
media_content_type="favorites_folder",
|
||||||
can_play=False,
|
can_play=False,
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
"""The spotify integration."""
|
"""The spotify integration."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from datetime import timedelta
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
|
import requests
|
||||||
from spotipy import Spotify, SpotifyException
|
from spotipy import Spotify, SpotifyException
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
@ -20,13 +25,16 @@ from homeassistant.helpers.config_entry_oauth2_flow import (
|
|||||||
async_get_config_entry_implementation,
|
async_get_config_entry_implementation,
|
||||||
)
|
)
|
||||||
from homeassistant.helpers.typing import ConfigType
|
from homeassistant.helpers.typing import ConfigType
|
||||||
|
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||||
|
|
||||||
from . import config_flow
|
from . import config_flow
|
||||||
from .const import (
|
from .const import (
|
||||||
DATA_SPOTIFY_CLIENT,
|
DATA_SPOTIFY_CLIENT,
|
||||||
|
DATA_SPOTIFY_DEVICES,
|
||||||
DATA_SPOTIFY_ME,
|
DATA_SPOTIFY_ME,
|
||||||
DATA_SPOTIFY_SESSION,
|
DATA_SPOTIFY_SESSION,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
|
LOGGER,
|
||||||
MEDIA_PLAYER_PREFIX,
|
MEDIA_PLAYER_PREFIX,
|
||||||
SPOTIFY_SCOPES,
|
SPOTIFY_SCOPES,
|
||||||
)
|
)
|
||||||
@ -112,9 +120,34 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
except SpotifyException as err:
|
except SpotifyException as err:
|
||||||
raise ConfigEntryNotReady from 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.setdefault(DOMAIN, {})
|
||||||
hass.data[DOMAIN][entry.entry_id] = {
|
hass.data[DOMAIN][entry.entry_id] = {
|
||||||
DATA_SPOTIFY_CLIENT: spotify,
|
DATA_SPOTIFY_CLIENT: spotify,
|
||||||
|
DATA_SPOTIFY_DEVICES: device_coordinator,
|
||||||
DATA_SPOTIFY_ME: current_user,
|
DATA_SPOTIFY_ME: current_user,
|
||||||
DATA_SPOTIFY_SESSION: session,
|
DATA_SPOTIFY_SESSION: session,
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,13 @@
|
|||||||
"""Define constants for the Spotify integration."""
|
"""Define constants for the Spotify integration."""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
DOMAIN = "spotify"
|
DOMAIN = "spotify"
|
||||||
|
|
||||||
|
LOGGER = logging.getLogger(__package__)
|
||||||
|
|
||||||
DATA_SPOTIFY_CLIENT = "spotify_client"
|
DATA_SPOTIFY_CLIENT = "spotify_client"
|
||||||
|
DATA_SPOTIFY_DEVICES = "spotify_devices"
|
||||||
DATA_SPOTIFY_ME = "spotify_me"
|
DATA_SPOTIFY_ME = "spotify_me"
|
||||||
DATA_SPOTIFY_SESSION = "spotify_session"
|
DATA_SPOTIFY_SESSION = "spotify_session"
|
||||||
|
|
||||||
|
@ -52,7 +52,7 @@ from homeassistant.const import (
|
|||||||
STATE_PAUSED,
|
STATE_PAUSED,
|
||||||
STATE_PLAYING,
|
STATE_PLAYING,
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
from homeassistant.helpers.config_entry_oauth2_flow import OAuth2Session
|
from homeassistant.helpers.config_entry_oauth2_flow import OAuth2Session
|
||||||
from homeassistant.helpers.device_registry import DeviceEntryType
|
from homeassistant.helpers.device_registry import DeviceEntryType
|
||||||
@ -62,6 +62,7 @@ from homeassistant.util.dt import utc_from_timestamp
|
|||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
DATA_SPOTIFY_CLIENT,
|
DATA_SPOTIFY_CLIENT,
|
||||||
|
DATA_SPOTIFY_DEVICES,
|
||||||
DATA_SPOTIFY_ME,
|
DATA_SPOTIFY_ME,
|
||||||
DATA_SPOTIFY_SESSION,
|
DATA_SPOTIFY_SESSION,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
@ -269,7 +270,6 @@ class SpotifyMediaPlayer(MediaPlayerEntity):
|
|||||||
)
|
)
|
||||||
|
|
||||||
self._currently_playing: dict | None = {}
|
self._currently_playing: dict | None = {}
|
||||||
self._devices: list[dict] | None = []
|
|
||||||
self._playlist: dict | None = None
|
self._playlist: dict | None = None
|
||||||
|
|
||||||
self._attr_name = self._name
|
self._attr_name = self._name
|
||||||
@ -290,6 +290,11 @@ class SpotifyMediaPlayer(MediaPlayerEntity):
|
|||||||
"""Return spotify API."""
|
"""Return spotify API."""
|
||||||
return self._spotify_data[DATA_SPOTIFY_CLIENT]
|
return self._spotify_data[DATA_SPOTIFY_CLIENT]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _devices(self) -> list:
|
||||||
|
"""Return spotify devices."""
|
||||||
|
return self._spotify_data[DATA_SPOTIFY_DEVICES].data
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def device_info(self) -> DeviceInfo:
|
def device_info(self) -> DeviceInfo:
|
||||||
"""Return device information about this entity."""
|
"""Return device information about this entity."""
|
||||||
@ -517,14 +522,14 @@ class SpotifyMediaPlayer(MediaPlayerEntity):
|
|||||||
current = self._spotify.current_playback()
|
current = self._spotify.current_playback()
|
||||||
self._currently_playing = current or {}
|
self._currently_playing = current or {}
|
||||||
|
|
||||||
self._playlist = None
|
|
||||||
context = self._currently_playing.get("context")
|
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"])
|
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):
|
async def async_browse_media(self, media_content_type=None, media_content_id=None):
|
||||||
"""Implement the websocket media browsing helper."""
|
"""Implement the websocket media browsing helper."""
|
||||||
|
|
||||||
@ -543,6 +548,22 @@ class SpotifyMediaPlayer(MediaPlayerEntity):
|
|||||||
media_content_id,
|
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(
|
async def async_browse_media_internal(
|
||||||
hass,
|
hass,
|
||||||
|
@ -338,7 +338,6 @@ class Stream:
|
|||||||
)
|
)
|
||||||
except StreamWorkerError as err:
|
except StreamWorkerError as err:
|
||||||
self._logger.error("Error from stream worker: %s", str(err))
|
self._logger.error("Error from stream worker: %s", str(err))
|
||||||
self._available = False
|
|
||||||
|
|
||||||
stream_state.discontinuity()
|
stream_state.discontinuity()
|
||||||
if not self.keepalive or self._thread_quit.is_set():
|
if not self.keepalive or self._thread_quit.is_set():
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
"domain": "synology_dsm",
|
"domain": "synology_dsm",
|
||||||
"name": "Synology DSM",
|
"name": "Synology DSM",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/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"],
|
"codeowners": ["@hacf-fr", "@Quentame", "@mib1185"],
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"ssdp": [
|
"ssdp": [
|
||||||
|
@ -7,7 +7,7 @@ from .backports.enum import StrEnum
|
|||||||
|
|
||||||
MAJOR_VERSION: Final = 2022
|
MAJOR_VERSION: Final = 2022
|
||||||
MINOR_VERSION: Final = 2
|
MINOR_VERSION: Final = 2
|
||||||
PATCH_VERSION: Final = "5"
|
PATCH_VERSION: Final = "6"
|
||||||
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
||||||
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
|
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
|
||||||
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 9, 0)
|
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),
|
"EntityCategory instead" % (type(value).__name__, value),
|
||||||
error_if_core=False,
|
error_if_core=False,
|
||||||
)
|
)
|
||||||
|
try:
|
||||||
return EntityCategory(value)
|
return EntityCategory(value)
|
||||||
|
except ValueError:
|
||||||
|
return None
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
@ -138,7 +138,7 @@ aio_georss_gdacs==0.5
|
|||||||
aioambient==2021.11.0
|
aioambient==2021.11.0
|
||||||
|
|
||||||
# homeassistant.components.aseko_pool_live
|
# homeassistant.components.aseko_pool_live
|
||||||
aioaseko==0.0.1
|
aioaseko==0.0.2
|
||||||
|
|
||||||
# homeassistant.components.asuswrt
|
# homeassistant.components.asuswrt
|
||||||
aioasuswrt==1.4.0
|
aioasuswrt==1.4.0
|
||||||
@ -166,7 +166,7 @@ aioeagle==1.1.0
|
|||||||
aioemonitor==1.0.5
|
aioemonitor==1.0.5
|
||||||
|
|
||||||
# homeassistant.components.esphome
|
# homeassistant.components.esphome
|
||||||
aioesphomeapi==10.8.1
|
aioesphomeapi==10.8.2
|
||||||
|
|
||||||
# homeassistant.components.flo
|
# homeassistant.components.flo
|
||||||
aioflo==2021.11.0
|
aioflo==2021.11.0
|
||||||
@ -764,7 +764,7 @@ google-cloud-pubsub==2.9.0
|
|||||||
google-cloud-texttospeech==0.4.0
|
google-cloud-texttospeech==0.4.0
|
||||||
|
|
||||||
# homeassistant.components.nest
|
# homeassistant.components.nest
|
||||||
google-nest-sdm==1.6.0
|
google-nest-sdm==1.7.1
|
||||||
|
|
||||||
# homeassistant.components.google_travel_time
|
# homeassistant.components.google_travel_time
|
||||||
googlemaps==2.5.1
|
googlemaps==2.5.1
|
||||||
@ -1049,7 +1049,7 @@ minio==5.0.10
|
|||||||
mitemp_bt==0.0.5
|
mitemp_bt==0.0.5
|
||||||
|
|
||||||
# homeassistant.components.motion_blinds
|
# homeassistant.components.motion_blinds
|
||||||
motionblinds==0.5.10
|
motionblinds==0.5.12
|
||||||
|
|
||||||
# homeassistant.components.motioneye
|
# homeassistant.components.motioneye
|
||||||
motioneye-client==0.3.12
|
motioneye-client==0.3.12
|
||||||
@ -1310,7 +1310,7 @@ pushbullet.py==0.11.0
|
|||||||
pushover_complete==1.1.1
|
pushover_complete==1.1.1
|
||||||
|
|
||||||
# homeassistant.components.pvoutput
|
# homeassistant.components.pvoutput
|
||||||
pvo==0.2.1
|
pvo==0.2.2
|
||||||
|
|
||||||
# homeassistant.components.rpi_gpio_pwm
|
# homeassistant.components.rpi_gpio_pwm
|
||||||
pwmled==1.6.7
|
pwmled==1.6.7
|
||||||
@ -1331,7 +1331,7 @@ py-nightscout==1.2.2
|
|||||||
py-schluter==0.1.7
|
py-schluter==0.1.7
|
||||||
|
|
||||||
# homeassistant.components.synology_dsm
|
# homeassistant.components.synology_dsm
|
||||||
py-synologydsm-api==1.0.5
|
py-synologydsm-api==1.0.6
|
||||||
|
|
||||||
# homeassistant.components.zabbix
|
# homeassistant.components.zabbix
|
||||||
py-zabbix==1.1.7
|
py-zabbix==1.1.7
|
||||||
@ -1500,7 +1500,7 @@ pyeight==0.2.0
|
|||||||
pyemby==1.8
|
pyemby==1.8
|
||||||
|
|
||||||
# homeassistant.components.envisalink
|
# homeassistant.components.envisalink
|
||||||
pyenvisalink==4.3
|
pyenvisalink==4.4
|
||||||
|
|
||||||
# homeassistant.components.ephember
|
# homeassistant.components.ephember
|
||||||
pyephember==0.3.1
|
pyephember==0.3.1
|
||||||
@ -1954,7 +1954,7 @@ python-mpd2==3.0.4
|
|||||||
python-mystrom==1.1.2
|
python-mystrom==1.1.2
|
||||||
|
|
||||||
# homeassistant.components.nest
|
# homeassistant.components.nest
|
||||||
python-nest==4.1.0
|
python-nest==4.2.0
|
||||||
|
|
||||||
# homeassistant.components.ozw
|
# homeassistant.components.ozw
|
||||||
python-openzwave-mqtt[mqtt-client]==1.4.0
|
python-openzwave-mqtt[mqtt-client]==1.4.0
|
||||||
@ -2513,7 +2513,7 @@ xs1-api-client==3.0.0
|
|||||||
yalesmartalarmclient==0.3.7
|
yalesmartalarmclient==0.3.7
|
||||||
|
|
||||||
# homeassistant.components.august
|
# homeassistant.components.august
|
||||||
yalexs==1.1.20
|
yalexs==1.1.22
|
||||||
|
|
||||||
# homeassistant.components.yeelight
|
# homeassistant.components.yeelight
|
||||||
yeelight==0.7.8
|
yeelight==0.7.8
|
||||||
|
@ -91,7 +91,7 @@ aio_georss_gdacs==0.5
|
|||||||
aioambient==2021.11.0
|
aioambient==2021.11.0
|
||||||
|
|
||||||
# homeassistant.components.aseko_pool_live
|
# homeassistant.components.aseko_pool_live
|
||||||
aioaseko==0.0.1
|
aioaseko==0.0.2
|
||||||
|
|
||||||
# homeassistant.components.asuswrt
|
# homeassistant.components.asuswrt
|
||||||
aioasuswrt==1.4.0
|
aioasuswrt==1.4.0
|
||||||
@ -119,7 +119,7 @@ aioeagle==1.1.0
|
|||||||
aioemonitor==1.0.5
|
aioemonitor==1.0.5
|
||||||
|
|
||||||
# homeassistant.components.esphome
|
# homeassistant.components.esphome
|
||||||
aioesphomeapi==10.8.1
|
aioesphomeapi==10.8.2
|
||||||
|
|
||||||
# homeassistant.components.flo
|
# homeassistant.components.flo
|
||||||
aioflo==2021.11.0
|
aioflo==2021.11.0
|
||||||
@ -492,7 +492,7 @@ google-api-python-client==1.6.4
|
|||||||
google-cloud-pubsub==2.9.0
|
google-cloud-pubsub==2.9.0
|
||||||
|
|
||||||
# homeassistant.components.nest
|
# homeassistant.components.nest
|
||||||
google-nest-sdm==1.6.0
|
google-nest-sdm==1.7.1
|
||||||
|
|
||||||
# homeassistant.components.google_travel_time
|
# homeassistant.components.google_travel_time
|
||||||
googlemaps==2.5.1
|
googlemaps==2.5.1
|
||||||
@ -655,7 +655,7 @@ millheater==0.9.0
|
|||||||
minio==5.0.10
|
minio==5.0.10
|
||||||
|
|
||||||
# homeassistant.components.motion_blinds
|
# homeassistant.components.motion_blinds
|
||||||
motionblinds==0.5.10
|
motionblinds==0.5.12
|
||||||
|
|
||||||
# homeassistant.components.motioneye
|
# homeassistant.components.motioneye
|
||||||
motioneye-client==0.3.12
|
motioneye-client==0.3.12
|
||||||
@ -814,7 +814,7 @@ pure-python-adb[async]==0.3.0.dev0
|
|||||||
pushbullet.py==0.11.0
|
pushbullet.py==0.11.0
|
||||||
|
|
||||||
# homeassistant.components.pvoutput
|
# homeassistant.components.pvoutput
|
||||||
pvo==0.2.1
|
pvo==0.2.2
|
||||||
|
|
||||||
# homeassistant.components.canary
|
# homeassistant.components.canary
|
||||||
py-canary==0.5.1
|
py-canary==0.5.1
|
||||||
@ -829,7 +829,7 @@ py-melissa-climate==2.1.4
|
|||||||
py-nightscout==1.2.2
|
py-nightscout==1.2.2
|
||||||
|
|
||||||
# homeassistant.components.synology_dsm
|
# homeassistant.components.synology_dsm
|
||||||
py-synologydsm-api==1.0.5
|
py-synologydsm-api==1.0.6
|
||||||
|
|
||||||
# homeassistant.components.seventeentrack
|
# homeassistant.components.seventeentrack
|
||||||
py17track==2021.12.2
|
py17track==2021.12.2
|
||||||
@ -1206,7 +1206,7 @@ python-kasa==0.4.1
|
|||||||
python-miio==0.5.9.2
|
python-miio==0.5.9.2
|
||||||
|
|
||||||
# homeassistant.components.nest
|
# homeassistant.components.nest
|
||||||
python-nest==4.1.0
|
python-nest==4.2.0
|
||||||
|
|
||||||
# homeassistant.components.ozw
|
# homeassistant.components.ozw
|
||||||
python-openzwave-mqtt[mqtt-client]==1.4.0
|
python-openzwave-mqtt[mqtt-client]==1.4.0
|
||||||
@ -1541,7 +1541,7 @@ xmltodict==0.12.0
|
|||||||
yalesmartalarmclient==0.3.7
|
yalesmartalarmclient==0.3.7
|
||||||
|
|
||||||
# homeassistant.components.august
|
# homeassistant.components.august
|
||||||
yalexs==1.1.20
|
yalexs==1.1.22
|
||||||
|
|
||||||
# homeassistant.components.yeelight
|
# homeassistant.components.yeelight
|
||||||
yeelight==0.7.8
|
yeelight==0.7.8
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[metadata]
|
[metadata]
|
||||||
name = homeassistant
|
name = homeassistant
|
||||||
version = 2022.2.5
|
version = 2022.2.6
|
||||||
author = The Home Assistant Authors
|
author = The Home Assistant Authors
|
||||||
author_email = hello@home-assistant.io
|
author_email = hello@home-assistant.io
|
||||||
license = Apache-2.0
|
license = Apache-2.0
|
||||||
|
@ -61,3 +61,25 @@ async def test_sensor(
|
|||||||
assert state.attributes.get(ATTR_ARCH) == "aargh"
|
assert state.attributes.get(ATTR_ARCH) == "aargh"
|
||||||
assert state.attributes.get(ATTR_BRAND) == "Intel Ryzen 7"
|
assert state.attributes.get(ATTR_BRAND) == "Intel Ryzen 7"
|
||||||
assert state.attributes.get(ATTR_HZ) == 3.6
|
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."""
|
"""The tests for the group cover platform."""
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
|
import async_timeout
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from homeassistant.components.cover import (
|
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_TILT).state == STATE_OPENING
|
||||||
assert hass.states.get(DEMO_COVER_POS).state == STATE_OPEN
|
assert hass.states.get(DEMO_COVER_POS).state == STATE_OPEN
|
||||||
assert hass.states.get(COVER_GROUP).state == STATE_OPENING
|
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."""
|
"""The tests for the group fan platform."""
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
import async_timeout
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from homeassistant import config as hass_config
|
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
|
assert percentage_full_fan_state.attributes[ATTR_DIRECTION] == DIRECTION_REVERSE
|
||||||
fan_group_state = hass.states.get(FAN_GROUP)
|
fan_group_state = hass.states.get(FAN_GROUP)
|
||||||
assert fan_group_state.attributes[ATTR_DIRECTION] == DIRECTION_REVERSE
|
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
|
import unittest.mock
|
||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
|
import async_timeout
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from homeassistant import config as hass_config
|
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):
|
async def test_nested_group(hass):
|
||||||
"""Test nested light group."""
|
"""Test nested light group."""
|
||||||
hass.states.async_set("light.kitchen", "on")
|
|
||||||
await async_setup_component(
|
await async_setup_component(
|
||||||
hass,
|
hass,
|
||||||
LIGHT_DOMAIN,
|
LIGHT_DOMAIN,
|
||||||
{
|
{
|
||||||
LIGHT_DOMAIN: [
|
LIGHT_DOMAIN: [
|
||||||
|
{"platform": "demo"},
|
||||||
{
|
{
|
||||||
"platform": DOMAIN,
|
"platform": DOMAIN,
|
||||||
"entities": ["light.bedroom_group"],
|
"entities": ["light.bedroom_group"],
|
||||||
@ -1483,7 +1484,7 @@ async def test_nested_group(hass):
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"platform": DOMAIN,
|
"platform": DOMAIN,
|
||||||
"entities": ["light.kitchen", "light.bedroom"],
|
"entities": ["light.bed_light", "light.kitchen_lights"],
|
||||||
"name": "Bedroom Group",
|
"name": "Bedroom Group",
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
@ -1496,9 +1497,25 @@ async def test_nested_group(hass):
|
|||||||
state = hass.states.get("light.bedroom_group")
|
state = hass.states.get("light.bedroom_group")
|
||||||
assert state is not None
|
assert state is not None
|
||||||
assert state.state == STATE_ON
|
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")
|
state = hass.states.get("light.nested_group")
|
||||||
assert state is not None
|
assert state is not None
|
||||||
assert state.state == STATE_ON
|
assert state.state == STATE_ON
|
||||||
assert state.attributes.get(ATTR_ENTITY_ID) == ["light.bedroom_group"]
|
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."""
|
"""The tests for the Media group platform."""
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
import async_timeout
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from homeassistant.components.group import DOMAIN
|
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):
|
async def test_nested_group(hass):
|
||||||
"""Test nested media group."""
|
"""Test nested media group."""
|
||||||
hass.states.async_set("media_player.player_1", "on")
|
|
||||||
await async_setup_component(
|
await async_setup_component(
|
||||||
hass,
|
hass,
|
||||||
MEDIA_DOMAIN,
|
MEDIA_DOMAIN,
|
||||||
{
|
{
|
||||||
MEDIA_DOMAIN: [
|
MEDIA_DOMAIN: [
|
||||||
|
{"platform": "demo"},
|
||||||
{
|
{
|
||||||
"platform": DOMAIN,
|
"platform": DOMAIN,
|
||||||
"entities": ["media_player.group_1"],
|
"entities": ["media_player.group_1"],
|
||||||
@ -499,7 +500,7 @@ async def test_nested_group(hass):
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"platform": DOMAIN,
|
"platform": DOMAIN,
|
||||||
"entities": ["media_player.player_1", "media_player.player_2"],
|
"entities": ["media_player.bedroom", "media_player.kitchen"],
|
||||||
"name": "Group 1",
|
"name": "Group 1",
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
@ -511,13 +512,28 @@ async def test_nested_group(hass):
|
|||||||
|
|
||||||
state = hass.states.get("media_player.group_1")
|
state = hass.states.get("media_player.group_1")
|
||||||
assert state is not None
|
assert state is not None
|
||||||
assert state.state == STATE_ON
|
assert state.state == STATE_PLAYING
|
||||||
assert state.attributes.get(ATTR_ENTITY_ID) == [
|
assert state.attributes.get(ATTR_ENTITY_ID) == [
|
||||||
"media_player.player_1",
|
"media_player.bedroom",
|
||||||
"media_player.player_2",
|
"media_player.kitchen",
|
||||||
]
|
]
|
||||||
|
|
||||||
state = hass.states.get("media_player.nested_group")
|
state = hass.states.get("media_player.nested_group")
|
||||||
assert state is not None
|
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"]
|
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_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(
|
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 result.get("step_id") == SOURCE_USER
|
||||||
assert "flow_id" in result
|
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(
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
result["flow_id"],
|
result["flow_id"],
|
||||||
user_input={
|
user_input={
|
||||||
@ -83,9 +83,9 @@ async def test_full_flow_with_authentication_error(
|
|||||||
assert "flow_id" in result2
|
assert "flow_id" in result2
|
||||||
|
|
||||||
assert len(mock_setup_entry.mock_calls) == 0
|
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(
|
result3 = await hass.config_entries.flow.async_configure(
|
||||||
result2["flow_id"],
|
result2["flow_id"],
|
||||||
user_input={
|
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_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(
|
async def test_connection_error(
|
||||||
hass: HomeAssistant, mock_pvoutput_config_flow: MagicMock
|
hass: HomeAssistant, mock_pvoutput_config_flow: MagicMock
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test API connection error."""
|
"""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(
|
result = await hass.config_entries.flow.async_init(
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
@ -123,7 +123,7 @@ async def test_connection_error(
|
|||||||
assert result.get("type") == RESULT_TYPE_FORM
|
assert result.get("type") == RESULT_TYPE_FORM
|
||||||
assert result.get("errors") == {"base": "cannot_connect"}
|
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(
|
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_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(
|
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_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(
|
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 result.get("step_id") == "reauth_confirm"
|
||||||
assert "flow_id" in result
|
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(
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
result["flow_id"],
|
result["flow_id"],
|
||||||
{CONF_API_KEY: "invalid_key"},
|
{CONF_API_KEY: "invalid_key"},
|
||||||
@ -256,9 +256,9 @@ async def test_reauth_with_authentication_error(
|
|||||||
assert "flow_id" in result2
|
assert "flow_id" in result2
|
||||||
|
|
||||||
assert len(mock_setup_entry.mock_calls) == 0
|
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(
|
result3 = await hass.config_entries.flow.async_configure(
|
||||||
result2["flow_id"],
|
result2["flow_id"],
|
||||||
user_input={CONF_API_KEY: "valid_key"},
|
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_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(
|
async def test_reauth_api_error(
|
||||||
@ -297,7 +297,7 @@ async def test_reauth_api_error(
|
|||||||
assert result.get("step_id") == "reauth_confirm"
|
assert result.get("step_id") == "reauth_confirm"
|
||||||
assert "flow_id" in result
|
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(
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
result["flow_id"],
|
result["flow_id"],
|
||||||
{CONF_API_KEY: "some_new_key"},
|
{CONF_API_KEY: "some_new_key"},
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
"""Tests for the PVOutput integration."""
|
"""Tests for the PVOutput integration."""
|
||||||
from unittest.mock import MagicMock
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
from pvo import PVOutputAuthenticationError, PVOutputConnectionError
|
from pvo import (
|
||||||
|
PVOutputAuthenticationError,
|
||||||
|
PVOutputConnectionError,
|
||||||
|
PVOutputNoDataError,
|
||||||
|
)
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from homeassistant.components.pvoutput.const import CONF_SYSTEM_ID, DOMAIN
|
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
|
assert mock_config_entry.state is ConfigEntryState.NOT_LOADED
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("side_effect", [PVOutputConnectionError, PVOutputNoDataError])
|
||||||
async def test_config_entry_not_ready(
|
async def test_config_entry_not_ready(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
mock_config_entry: MockConfigEntry,
|
mock_config_entry: MockConfigEntry,
|
||||||
mock_pvoutput: MagicMock,
|
mock_pvoutput: MagicMock,
|
||||||
|
side_effect: Exception,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test the PVOutput configuration entry not ready."""
|
"""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)
|
mock_config_entry.add_to_hass(hass)
|
||||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
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 entry.disabled_by is er.RegistryEntryDisabler.USER
|
||||||
assert " str for entity registry disabled_by. This is deprecated " in caplog.text
|
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