mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 11:17:21 +00:00
Merge pull request #71376 from home-assistant/rc
This commit is contained in:
commit
97c7d40d8a
@ -123,7 +123,7 @@ class AirzoneClimate(AirzoneZoneEntity, ClimateEntity):
|
|||||||
}
|
}
|
||||||
_LOGGER.debug("update_hvac_params=%s", _params)
|
_LOGGER.debug("update_hvac_params=%s", _params)
|
||||||
try:
|
try:
|
||||||
await self.coordinator.airzone.put_hvac(_params)
|
await self.coordinator.airzone.set_hvac_parameters(_params)
|
||||||
except AirzoneError as error:
|
except AirzoneError as error:
|
||||||
raise HomeAssistantError(
|
raise HomeAssistantError(
|
||||||
f"Failed to set zone {self.name}: {error}"
|
f"Failed to set zone {self.name}: {error}"
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"name": "Airzone",
|
"name": "Airzone",
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/airzone",
|
"documentation": "https://www.home-assistant.io/integrations/airzone",
|
||||||
"requirements": ["aioairzone==0.4.2"],
|
"requirements": ["aioairzone==0.4.3"],
|
||||||
"codeowners": ["@Noltari"],
|
"codeowners": ["@Noltari"],
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"loggers": ["aioairzone"]
|
"loggers": ["aioairzone"]
|
||||||
|
@ -296,7 +296,7 @@ class AppleTvMediaPlayer(AppleTVEntity, MediaPlayerEntity):
|
|||||||
_LOGGER.debug("Streaming %s via RAOP", media_id)
|
_LOGGER.debug("Streaming %s via RAOP", media_id)
|
||||||
await self.atv.stream.stream_file(media_id)
|
await self.atv.stream.stream_file(media_id)
|
||||||
|
|
||||||
if self._is_feature_available(FeatureName.PlayUrl):
|
elif self._is_feature_available(FeatureName.PlayUrl):
|
||||||
_LOGGER.debug("Playing %s via AirPlay", media_id)
|
_LOGGER.debug("Playing %s via AirPlay", media_id)
|
||||||
await self.atv.stream.play_url(media_id)
|
await self.atv.stream.play_url(media_id)
|
||||||
else:
|
else:
|
||||||
|
@ -58,7 +58,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
"""Set up Cast from a config entry."""
|
"""Set up Cast from a config entry."""
|
||||||
await home_assistant_cast.async_setup_ha_cast(hass, entry)
|
await home_assistant_cast.async_setup_ha_cast(hass, entry)
|
||||||
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
|
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
|
||||||
hass.data[DOMAIN] = {}
|
hass.data[DOMAIN] = {"cast_platform": {}, "unknown_models": {}}
|
||||||
await async_process_integration_platforms(hass, DOMAIN, _register_cast_platform)
|
await async_process_integration_platforms(hass, DOMAIN, _register_cast_platform)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@ -107,7 +107,7 @@ async def _register_cast_platform(
|
|||||||
or not hasattr(platform, "async_play_media")
|
or not hasattr(platform, "async_play_media")
|
||||||
):
|
):
|
||||||
raise HomeAssistantError(f"Invalid cast platform {platform}")
|
raise HomeAssistantError(f"Invalid cast platform {platform}")
|
||||||
hass.data[DOMAIN][integration_domain] = platform
|
hass.data[DOMAIN]["cast_platform"][integration_domain] = platform
|
||||||
|
|
||||||
|
|
||||||
async def async_remove_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
|
async def async_remove_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
|
||||||
|
@ -34,7 +34,7 @@ def discover_chromecast(
|
|||||||
_LOGGER.error("Discovered chromecast without uuid %s", info)
|
_LOGGER.error("Discovered chromecast without uuid %s", info)
|
||||||
return
|
return
|
||||||
|
|
||||||
info = info.fill_out_missing_chromecast_info()
|
info = info.fill_out_missing_chromecast_info(hass)
|
||||||
_LOGGER.debug("Discovered new or updated chromecast %s", info)
|
_LOGGER.debug("Discovered new or updated chromecast %s", info)
|
||||||
|
|
||||||
dispatcher_send(hass, SIGNAL_CAST_DISCOVERED, info)
|
dispatcher_send(hass, SIGNAL_CAST_DISCOVERED, info)
|
||||||
|
@ -15,8 +15,11 @@ from pychromecast import dial
|
|||||||
from pychromecast.const import CAST_TYPE_GROUP
|
from pychromecast.const import CAST_TYPE_GROUP
|
||||||
from pychromecast.models import CastInfo
|
from pychromecast.models import CastInfo
|
||||||
|
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers import aiohttp_client
|
from homeassistant.helpers import aiohttp_client
|
||||||
|
|
||||||
|
from .const import DOMAIN
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
_PLS_SECTION_PLAYLIST = "playlist"
|
_PLS_SECTION_PLAYLIST = "playlist"
|
||||||
@ -47,18 +50,50 @@ class ChromecastInfo:
|
|||||||
"""Return the UUID."""
|
"""Return the UUID."""
|
||||||
return self.cast_info.uuid
|
return self.cast_info.uuid
|
||||||
|
|
||||||
def fill_out_missing_chromecast_info(self) -> ChromecastInfo:
|
def fill_out_missing_chromecast_info(self, hass: HomeAssistant) -> ChromecastInfo:
|
||||||
"""Return a new ChromecastInfo object with missing attributes filled in.
|
"""Return a new ChromecastInfo object with missing attributes filled in.
|
||||||
|
|
||||||
Uses blocking HTTP / HTTPS.
|
Uses blocking HTTP / HTTPS.
|
||||||
"""
|
"""
|
||||||
cast_info = self.cast_info
|
cast_info = self.cast_info
|
||||||
if self.cast_info.cast_type is None or self.cast_info.manufacturer is None:
|
if self.cast_info.cast_type is None or self.cast_info.manufacturer is None:
|
||||||
# Manufacturer and cast type is not available in mDNS data, get it over http
|
unknown_models = hass.data[DOMAIN]["unknown_models"]
|
||||||
cast_info = dial.get_cast_type(
|
if self.cast_info.model_name not in unknown_models:
|
||||||
cast_info,
|
# Manufacturer and cast type is not available in mDNS data, get it over http
|
||||||
zconf=ChromeCastZeroconf.get_zeroconf(),
|
cast_info = dial.get_cast_type(
|
||||||
)
|
cast_info,
|
||||||
|
zconf=ChromeCastZeroconf.get_zeroconf(),
|
||||||
|
)
|
||||||
|
unknown_models[self.cast_info.model_name] = (
|
||||||
|
cast_info.cast_type,
|
||||||
|
cast_info.manufacturer,
|
||||||
|
)
|
||||||
|
|
||||||
|
report_issue = (
|
||||||
|
"create a bug report at "
|
||||||
|
"https://github.com/home-assistant/core/issues?q=is%3Aopen+is%3Aissue"
|
||||||
|
"+label%3A%22integration%3A+cast%22"
|
||||||
|
)
|
||||||
|
|
||||||
|
_LOGGER.info(
|
||||||
|
"Fetched cast details for unknown model '%s' manufacturer: '%s', type: '%s'. Please %s",
|
||||||
|
cast_info.model_name,
|
||||||
|
cast_info.manufacturer,
|
||||||
|
cast_info.cast_type,
|
||||||
|
report_issue,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
cast_type, manufacturer = unknown_models[self.cast_info.model_name]
|
||||||
|
cast_info = CastInfo(
|
||||||
|
cast_info.services,
|
||||||
|
cast_info.uuid,
|
||||||
|
cast_info.model_name,
|
||||||
|
cast_info.friendly_name,
|
||||||
|
cast_info.host,
|
||||||
|
cast_info.port,
|
||||||
|
cast_type,
|
||||||
|
manufacturer,
|
||||||
|
)
|
||||||
|
|
||||||
if not self.is_audio_group or self.is_dynamic_group is not None:
|
if not self.is_audio_group or self.is_dynamic_group is not None:
|
||||||
# We have all information, no need to check HTTP API.
|
# We have all information, no need to check HTTP API.
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"name": "Google Cast",
|
"name": "Google Cast",
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/cast",
|
"documentation": "https://www.home-assistant.io/integrations/cast",
|
||||||
"requirements": ["pychromecast==12.0.0"],
|
"requirements": ["pychromecast==12.1.1"],
|
||||||
"after_dependencies": [
|
"after_dependencies": [
|
||||||
"cloud",
|
"cloud",
|
||||||
"http",
|
"http",
|
||||||
|
@ -535,7 +535,7 @@ class CastMediaPlayerEntity(CastDevice, MediaPlayerEntity):
|
|||||||
"""Generate root node."""
|
"""Generate root node."""
|
||||||
children = []
|
children = []
|
||||||
# Add media browsers
|
# Add media browsers
|
||||||
for platform in self.hass.data[CAST_DOMAIN].values():
|
for platform in self.hass.data[CAST_DOMAIN]["cast_platform"].values():
|
||||||
children.extend(
|
children.extend(
|
||||||
await platform.async_get_media_browser_root_object(
|
await platform.async_get_media_browser_root_object(
|
||||||
self.hass, self._chromecast.cast_type
|
self.hass, self._chromecast.cast_type
|
||||||
@ -587,7 +587,7 @@ class CastMediaPlayerEntity(CastDevice, MediaPlayerEntity):
|
|||||||
if media_content_id is None:
|
if media_content_id is None:
|
||||||
return await self._async_root_payload(content_filter)
|
return await self._async_root_payload(content_filter)
|
||||||
|
|
||||||
for platform in self.hass.data[CAST_DOMAIN].values():
|
for platform in self.hass.data[CAST_DOMAIN]["cast_platform"].values():
|
||||||
browse_media = await platform.async_browse_media(
|
browse_media = await platform.async_browse_media(
|
||||||
self.hass,
|
self.hass,
|
||||||
media_content_type,
|
media_content_type,
|
||||||
@ -646,7 +646,7 @@ class CastMediaPlayerEntity(CastDevice, MediaPlayerEntity):
|
|||||||
return
|
return
|
||||||
|
|
||||||
# Try the cast platforms
|
# Try the cast platforms
|
||||||
for platform in self.hass.data[CAST_DOMAIN].values():
|
for platform in self.hass.data[CAST_DOMAIN]["cast_platform"].values():
|
||||||
result = await platform.async_play_media(
|
result = await platform.async_play_media(
|
||||||
self.hass, self.entity_id, self._chromecast, media_type, media_id
|
self.hass, self.entity_id, self._chromecast, media_type, media_id
|
||||||
)
|
)
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
"domain": "compensation",
|
"domain": "compensation",
|
||||||
"name": "Compensation",
|
"name": "Compensation",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/compensation",
|
"documentation": "https://www.home-assistant.io/integrations/compensation",
|
||||||
"requirements": ["numpy==1.21.4"],
|
"requirements": ["numpy==1.21.6"],
|
||||||
"codeowners": ["@Petro31"],
|
"codeowners": ["@Petro31"],
|
||||||
"iot_class": "calculated"
|
"iot_class": "calculated"
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ from urllib.parse import urlparse
|
|||||||
from devolo_home_control_api.devices.zwave import Zwave
|
from devolo_home_control_api.devices.zwave import Zwave
|
||||||
from devolo_home_control_api.homecontrol import HomeControl
|
from devolo_home_control_api.homecontrol import HomeControl
|
||||||
|
|
||||||
|
from homeassistant.components.sensor import SensorDeviceClass
|
||||||
from homeassistant.helpers.entity import DeviceInfo, Entity
|
from homeassistant.helpers.entity import DeviceInfo, Entity
|
||||||
|
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
@ -71,7 +72,11 @@ class DevoloDeviceEntity(Entity):
|
|||||||
|
|
||||||
def _generic_message(self, message: tuple) -> None:
|
def _generic_message(self, message: tuple) -> None:
|
||||||
"""Handle generic messages."""
|
"""Handle generic messages."""
|
||||||
if len(message) == 3 and message[2] == "battery_level":
|
if (
|
||||||
|
len(message) == 3
|
||||||
|
and message[2] == "battery_level"
|
||||||
|
and self.device_class == SensorDeviceClass.BATTERY
|
||||||
|
):
|
||||||
self._value = message[1]
|
self._value = message[1]
|
||||||
elif len(message) == 3 and message[2] == "status":
|
elif len(message) == 3 and message[2] == "status":
|
||||||
# Maybe the API wants to tell us, that the device went on- or offline.
|
# Maybe the API wants to tell us, that the device went on- or offline.
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"name": "IQVIA",
|
"name": "IQVIA",
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/iqvia",
|
"documentation": "https://www.home-assistant.io/integrations/iqvia",
|
||||||
"requirements": ["numpy==1.21.4", "pyiqvia==2022.04.0"],
|
"requirements": ["numpy==1.21.6", "pyiqvia==2022.04.0"],
|
||||||
"codeowners": ["@bachya"],
|
"codeowners": ["@bachya"],
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"loggers": ["pyiqvia"]
|
"loggers": ["pyiqvia"]
|
||||||
|
@ -38,6 +38,7 @@ from .const import (
|
|||||||
CONF_CA_CERTS,
|
CONF_CA_CERTS,
|
||||||
CONF_CERTFILE,
|
CONF_CERTFILE,
|
||||||
CONF_KEYFILE,
|
CONF_KEYFILE,
|
||||||
|
CONFIG_URL,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
LUTRON_CASETA_BUTTON_EVENT,
|
LUTRON_CASETA_BUTTON_EVENT,
|
||||||
MANUFACTURER,
|
MANUFACTURER,
|
||||||
@ -306,13 +307,15 @@ class LutronCasetaDevice(Entity):
|
|||||||
self._device = device
|
self._device = device
|
||||||
self._smartbridge = bridge
|
self._smartbridge = bridge
|
||||||
self._bridge_device = bridge_device
|
self._bridge_device = bridge_device
|
||||||
|
if "serial" not in self._device:
|
||||||
|
return
|
||||||
info = DeviceInfo(
|
info = DeviceInfo(
|
||||||
identifiers={(DOMAIN, self.serial)},
|
identifiers={(DOMAIN, self.serial)},
|
||||||
manufacturer=MANUFACTURER,
|
manufacturer=MANUFACTURER,
|
||||||
model=f"{device['model']} ({device['type']})",
|
model=f"{device['model']} ({device['type']})",
|
||||||
name=self.name,
|
name=self.name,
|
||||||
via_device=(DOMAIN, self._bridge_device["serial"]),
|
via_device=(DOMAIN, self._bridge_device["serial"]),
|
||||||
configuration_url="https://device-login.lutron.com",
|
configuration_url=CONFIG_URL,
|
||||||
)
|
)
|
||||||
area, _ = _area_and_name_from_name(device["name"])
|
area, _ = _area_and_name_from_name(device["name"])
|
||||||
if area != UNASSIGNED_AREA:
|
if area != UNASSIGNED_AREA:
|
||||||
|
@ -6,11 +6,14 @@ from homeassistant.components.binary_sensor import (
|
|||||||
BinarySensorEntity,
|
BinarySensorEntity,
|
||||||
)
|
)
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.const import ATTR_SUGGESTED_AREA
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers.device_registry import DeviceEntryType
|
||||||
|
from homeassistant.helpers.entity import DeviceInfo
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
|
||||||
from . import DOMAIN as CASETA_DOMAIN, LutronCasetaDevice
|
from . import DOMAIN as CASETA_DOMAIN, LutronCasetaDevice, _area_and_name_from_name
|
||||||
from .const import BRIDGE_DEVICE, BRIDGE_LEAP
|
from .const import BRIDGE_DEVICE, BRIDGE_LEAP, CONFIG_URL, MANUFACTURER, UNASSIGNED_AREA
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
@ -39,6 +42,23 @@ async def async_setup_entry(
|
|||||||
class LutronOccupancySensor(LutronCasetaDevice, BinarySensorEntity):
|
class LutronOccupancySensor(LutronCasetaDevice, BinarySensorEntity):
|
||||||
"""Representation of a Lutron occupancy group."""
|
"""Representation of a Lutron occupancy group."""
|
||||||
|
|
||||||
|
def __init__(self, device, bridge, bridge_device):
|
||||||
|
"""Init an occupancy sensor."""
|
||||||
|
super().__init__(device, bridge, bridge_device)
|
||||||
|
info = DeviceInfo(
|
||||||
|
identifiers={(CASETA_DOMAIN, self.unique_id)},
|
||||||
|
manufacturer=MANUFACTURER,
|
||||||
|
model="Lutron Occupancy",
|
||||||
|
name=self.name,
|
||||||
|
via_device=(CASETA_DOMAIN, self._bridge_device["serial"]),
|
||||||
|
configuration_url=CONFIG_URL,
|
||||||
|
entry_type=DeviceEntryType.SERVICE,
|
||||||
|
)
|
||||||
|
area, _ = _area_and_name_from_name(device["name"])
|
||||||
|
if area != UNASSIGNED_AREA:
|
||||||
|
info[ATTR_SUGGESTED_AREA] = area
|
||||||
|
self._attr_device_info = info
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def device_class(self):
|
def device_class(self):
|
||||||
"""Flag supported features."""
|
"""Flag supported features."""
|
||||||
@ -65,16 +85,6 @@ class LutronOccupancySensor(LutronCasetaDevice, BinarySensorEntity):
|
|||||||
"""Return a unique identifier."""
|
"""Return a unique identifier."""
|
||||||
return f"occupancygroup_{self.device_id}"
|
return f"occupancygroup_{self.device_id}"
|
||||||
|
|
||||||
@property
|
|
||||||
def device_info(self):
|
|
||||||
"""Return the device info.
|
|
||||||
|
|
||||||
Sensor entities are aggregated from one or more physical
|
|
||||||
sensors by each room. Therefore, there shouldn't be devices
|
|
||||||
related to any sensor entities.
|
|
||||||
"""
|
|
||||||
return None
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def extra_state_attributes(self):
|
def extra_state_attributes(self):
|
||||||
"""Return the state attributes."""
|
"""Return the state attributes."""
|
||||||
|
@ -35,3 +35,5 @@ CONF_SUBTYPE = "subtype"
|
|||||||
BRIDGE_TIMEOUT = 35
|
BRIDGE_TIMEOUT = 35
|
||||||
|
|
||||||
UNASSIGNED_AREA = "Unassigned"
|
UNASSIGNED_AREA = "Unassigned"
|
||||||
|
|
||||||
|
CONFIG_URL = "https://device-login.lutron.com"
|
||||||
|
@ -146,13 +146,13 @@ async def async_setup_entry(
|
|||||||
if not coordinator.last_update_success:
|
if not coordinator.last_update_success:
|
||||||
return
|
return
|
||||||
|
|
||||||
devices = coordinator.data
|
devices: dict[str, MeaterProbe] = coordinator.data
|
||||||
entities = []
|
entities = []
|
||||||
known_probes: set = hass.data[DOMAIN]["known_probes"]
|
known_probes: set = hass.data[DOMAIN]["known_probes"]
|
||||||
|
|
||||||
# Add entities for temperature probes which we've not yet seen
|
# Add entities for temperature probes which we've not yet seen
|
||||||
for dev in devices:
|
for dev in devices:
|
||||||
if dev.id in known_probes:
|
if dev in known_probes:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
entities.extend(
|
entities.extend(
|
||||||
@ -161,7 +161,7 @@ async def async_setup_entry(
|
|||||||
for sensor_description in SENSOR_TYPES
|
for sensor_description in SENSOR_TYPES
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
known_probes.add(dev.id)
|
known_probes.add(dev)
|
||||||
|
|
||||||
async_add_entities(entities)
|
async_add_entities(entities)
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"name": "Nettigo Air Monitor",
|
"name": "Nettigo Air Monitor",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/nam",
|
"documentation": "https://www.home-assistant.io/integrations/nam",
|
||||||
"codeowners": ["@bieniu"],
|
"codeowners": ["@bieniu"],
|
||||||
"requirements": ["nettigo-air-monitor==1.2.2"],
|
"requirements": ["nettigo-air-monitor==1.2.3"],
|
||||||
"zeroconf": [
|
"zeroconf": [
|
||||||
{
|
{
|
||||||
"type": "_http._tcp.local.",
|
"type": "_http._tcp.local.",
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
"domain": "opencv",
|
"domain": "opencv",
|
||||||
"name": "OpenCV",
|
"name": "OpenCV",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/opencv",
|
"documentation": "https://www.home-assistant.io/integrations/opencv",
|
||||||
"requirements": ["numpy==1.21.4", "opencv-python-headless==4.5.2.54"],
|
"requirements": ["numpy==1.21.6", "opencv-python-headless==4.5.2.54"],
|
||||||
"codeowners": [],
|
"codeowners": [],
|
||||||
"iot_class": "local_push"
|
"iot_class": "local_push"
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ from homeassistant.components import cloud
|
|||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import CONF_API_KEY, Platform
|
from homeassistant.const import CONF_API_KEY, Platform
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.exceptions import ConfigEntryNotReady
|
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
|
||||||
from homeassistant.helpers import config_validation as cv
|
from homeassistant.helpers import config_validation as cv
|
||||||
|
|
||||||
from .const import CONF_CLOUDHOOK_URL, CONF_MANUAL_RUN_MINS, CONF_WEBHOOK_ID, DOMAIN
|
from .const import CONF_CLOUDHOOK_URL, CONF_MANUAL_RUN_MINS, CONF_WEBHOOK_ID, DOMAIN
|
||||||
@ -73,6 +73,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
# Get the API user
|
# Get the API user
|
||||||
try:
|
try:
|
||||||
await person.async_setup(hass)
|
await person.async_setup(hass)
|
||||||
|
except ConfigEntryAuthFailed as error:
|
||||||
|
# Reauth is not yet implemented
|
||||||
|
_LOGGER.error("Authentication failed: %s", error)
|
||||||
|
return False
|
||||||
except ConnectTimeout as error:
|
except ConnectTimeout as error:
|
||||||
_LOGGER.error("Could not reach the Rachio API: %s", error)
|
_LOGGER.error("Could not reach the Rachio API: %s", error)
|
||||||
raise ConfigEntryNotReady from error
|
raise ConfigEntryNotReady from error
|
||||||
|
@ -8,6 +8,7 @@ import voluptuous as vol
|
|||||||
|
|
||||||
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
|
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
|
||||||
from homeassistant.core import ServiceCall
|
from homeassistant.core import ServiceCall
|
||||||
|
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
|
||||||
from homeassistant.helpers import config_validation as cv
|
from homeassistant.helpers import config_validation as cv
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
@ -125,12 +126,18 @@ class RachioPerson:
|
|||||||
rachio = self.rachio
|
rachio = self.rachio
|
||||||
|
|
||||||
response = rachio.person.info()
|
response = rachio.person.info()
|
||||||
assert int(response[0][KEY_STATUS]) == HTTPStatus.OK, "API key error"
|
if is_invalid_auth_code(int(response[0][KEY_STATUS])):
|
||||||
|
raise ConfigEntryAuthFailed(f"API key error: {response}")
|
||||||
|
if int(response[0][KEY_STATUS]) != HTTPStatus.OK:
|
||||||
|
raise ConfigEntryNotReady(f"API Error: {response}")
|
||||||
self._id = response[1][KEY_ID]
|
self._id = response[1][KEY_ID]
|
||||||
|
|
||||||
# Use user ID to get user data
|
# Use user ID to get user data
|
||||||
data = rachio.person.get(self._id)
|
data = rachio.person.get(self._id)
|
||||||
assert int(data[0][KEY_STATUS]) == HTTPStatus.OK, "User ID error"
|
if is_invalid_auth_code(int(data[0][KEY_STATUS])):
|
||||||
|
raise ConfigEntryAuthFailed(f"User ID error: {data}")
|
||||||
|
if int(data[0][KEY_STATUS]) != HTTPStatus.OK:
|
||||||
|
raise ConfigEntryNotReady(f"API Error: {data}")
|
||||||
self.username = data[1][KEY_USERNAME]
|
self.username = data[1][KEY_USERNAME]
|
||||||
devices = data[1][KEY_DEVICES]
|
devices = data[1][KEY_DEVICES]
|
||||||
for controller in devices:
|
for controller in devices:
|
||||||
@ -297,3 +304,11 @@ class RachioIro:
|
|||||||
"""Resume paused watering on this controller."""
|
"""Resume paused watering on this controller."""
|
||||||
self.rachio.device.resume_zone_run(self.controller_id)
|
self.rachio.device.resume_zone_run(self.controller_id)
|
||||||
_LOGGER.debug("Resuming watering on %s", self)
|
_LOGGER.debug("Resuming watering on %s", self)
|
||||||
|
|
||||||
|
|
||||||
|
def is_invalid_auth_code(http_status_code):
|
||||||
|
"""HTTP status codes that mean invalid auth."""
|
||||||
|
if http_status_code in (HTTPStatus.UNAUTHORIZED, HTTPStatus.FORBIDDEN):
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
@ -3,8 +3,6 @@
|
|||||||
"name": "SABnzbd",
|
"name": "SABnzbd",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/sabnzbd",
|
"documentation": "https://www.home-assistant.io/integrations/sabnzbd",
|
||||||
"requirements": ["pysabnzbd==1.1.1"],
|
"requirements": ["pysabnzbd==1.1.1"],
|
||||||
"dependencies": ["configurator"],
|
|
||||||
"after_dependencies": ["discovery"],
|
|
||||||
"codeowners": ["@shaiu"],
|
"codeowners": ["@shaiu"],
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
|
@ -14,8 +14,10 @@ from . import DOMAIN, SIGNAL_SABNZBD_UPDATED
|
|||||||
from ...config_entries import ConfigEntry
|
from ...config_entries import ConfigEntry
|
||||||
from ...const import DATA_GIGABYTES, DATA_MEGABYTES, DATA_RATE_MEGABYTES_PER_SECOND
|
from ...const import DATA_GIGABYTES, DATA_MEGABYTES, DATA_RATE_MEGABYTES_PER_SECOND
|
||||||
from ...core import HomeAssistant
|
from ...core import HomeAssistant
|
||||||
|
from ...helpers.device_registry import DeviceEntryType
|
||||||
|
from ...helpers.entity import DeviceInfo
|
||||||
from ...helpers.entity_platform import AddEntitiesCallback
|
from ...helpers.entity_platform import AddEntitiesCallback
|
||||||
from .const import KEY_API_DATA, KEY_NAME
|
from .const import DEFAULT_NAME, KEY_API_DATA, KEY_NAME
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@ -127,9 +129,16 @@ class SabnzbdSensor(SensorEntity):
|
|||||||
self, sabnzbd_api_data, client_name, description: SabnzbdSensorEntityDescription
|
self, sabnzbd_api_data, client_name, description: SabnzbdSensorEntityDescription
|
||||||
):
|
):
|
||||||
"""Initialize the sensor."""
|
"""Initialize the sensor."""
|
||||||
|
unique_id = description.key
|
||||||
|
self._attr_unique_id = unique_id
|
||||||
self.entity_description = description
|
self.entity_description = description
|
||||||
self._sabnzbd_api = sabnzbd_api_data
|
self._sabnzbd_api = sabnzbd_api_data
|
||||||
self._attr_name = f"{client_name} {description.name}"
|
self._attr_name = f"{client_name} {description.name}"
|
||||||
|
self._attr_device_info = DeviceInfo(
|
||||||
|
entry_type=DeviceEntryType.SERVICE,
|
||||||
|
identifiers={(DOMAIN, DOMAIN)},
|
||||||
|
name=DEFAULT_NAME,
|
||||||
|
)
|
||||||
|
|
||||||
async def async_added_to_hass(self):
|
async def async_added_to_hass(self):
|
||||||
"""Call when entity about to be added to hass."""
|
"""Call when entity about to be added to hass."""
|
||||||
|
@ -30,7 +30,12 @@ import homeassistant.helpers.config_validation as cv
|
|||||||
from homeassistant.helpers.debounce import Debouncer
|
from homeassistant.helpers.debounce import Debouncer
|
||||||
from homeassistant.helpers.typing import ConfigType
|
from homeassistant.helpers.typing import ConfigType
|
||||||
|
|
||||||
from .bridge import SamsungTVBridge, async_get_device_info, mac_from_device_info
|
from .bridge import (
|
||||||
|
SamsungTVBridge,
|
||||||
|
async_get_device_info,
|
||||||
|
mac_from_device_info,
|
||||||
|
model_requires_encryption,
|
||||||
|
)
|
||||||
from .const import (
|
from .const import (
|
||||||
CONF_ON_ACTION,
|
CONF_ON_ACTION,
|
||||||
CONF_SESSION_ID,
|
CONF_SESSION_ID,
|
||||||
@ -214,11 +219,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def _model_requires_encryption(model: str | None) -> bool:
|
|
||||||
"""H and J models need pairing with PIN."""
|
|
||||||
return model is not None and len(model) > 4 and model[4] in ("H", "J")
|
|
||||||
|
|
||||||
|
|
||||||
async def _async_create_bridge_with_updated_data(
|
async def _async_create_bridge_with_updated_data(
|
||||||
hass: HomeAssistant, entry: ConfigEntry
|
hass: HomeAssistant, entry: ConfigEntry
|
||||||
) -> SamsungTVBridge:
|
) -> SamsungTVBridge:
|
||||||
@ -279,7 +279,7 @@ async def _async_create_bridge_with_updated_data(
|
|||||||
LOGGER.info("Updated model to %s for %s", model, host)
|
LOGGER.info("Updated model to %s for %s", model, host)
|
||||||
updated_data[CONF_MODEL] = model
|
updated_data[CONF_MODEL] = model
|
||||||
|
|
||||||
if _model_requires_encryption(model) and method != METHOD_ENCRYPTED_WEBSOCKET:
|
if model_requires_encryption(model) and method != METHOD_ENCRYPTED_WEBSOCKET:
|
||||||
LOGGER.info(
|
LOGGER.info(
|
||||||
"Detected model %s for %s. Some televisions from H and J series use "
|
"Detected model %s for %s. Some televisions from H and J series use "
|
||||||
"an encrypted protocol but you are using %s which may not be supported",
|
"an encrypted protocol but you are using %s which may not be supported",
|
||||||
|
@ -85,6 +85,11 @@ def mac_from_device_info(info: dict[str, Any]) -> str | None:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def model_requires_encryption(model: str | None) -> bool:
|
||||||
|
"""H and J models need pairing with PIN."""
|
||||||
|
return model is not None and len(model) > 4 and model[4] in ("H", "J")
|
||||||
|
|
||||||
|
|
||||||
async def async_get_device_info(
|
async def async_get_device_info(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
host: str,
|
host: str,
|
||||||
@ -99,17 +104,19 @@ async def async_get_device_info(
|
|||||||
port,
|
port,
|
||||||
info,
|
info,
|
||||||
)
|
)
|
||||||
encrypted_bridge = SamsungTVEncryptedBridge(
|
# Check the encrypted port if the model requires encryption
|
||||||
hass, METHOD_ENCRYPTED_WEBSOCKET, host, ENCRYPTED_WEBSOCKET_PORT
|
if model_requires_encryption(info.get("device", {}).get("modelName")):
|
||||||
)
|
encrypted_bridge = SamsungTVEncryptedBridge(
|
||||||
result = await encrypted_bridge.async_try_connect()
|
hass, METHOD_ENCRYPTED_WEBSOCKET, host, ENCRYPTED_WEBSOCKET_PORT
|
||||||
if result != RESULT_CANNOT_CONNECT:
|
|
||||||
return (
|
|
||||||
result,
|
|
||||||
ENCRYPTED_WEBSOCKET_PORT,
|
|
||||||
METHOD_ENCRYPTED_WEBSOCKET,
|
|
||||||
info,
|
|
||||||
)
|
)
|
||||||
|
result = await encrypted_bridge.async_try_connect()
|
||||||
|
if result != RESULT_CANNOT_CONNECT:
|
||||||
|
return (
|
||||||
|
result,
|
||||||
|
ENCRYPTED_WEBSOCKET_PORT,
|
||||||
|
METHOD_ENCRYPTED_WEBSOCKET,
|
||||||
|
info,
|
||||||
|
)
|
||||||
return RESULT_SUCCESS, port, METHOD_WEBSOCKET, info
|
return RESULT_SUCCESS, port, METHOD_WEBSOCKET, info
|
||||||
|
|
||||||
# Try legacy port
|
# Try legacy port
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
"tensorflow==2.5.0",
|
"tensorflow==2.5.0",
|
||||||
"tf-models-official==2.5.0",
|
"tf-models-official==2.5.0",
|
||||||
"pycocotools==2.0.1",
|
"pycocotools==2.0.1",
|
||||||
"numpy==1.21.4",
|
"numpy==1.21.6",
|
||||||
"pillow==9.1.0"
|
"pillow==9.1.0"
|
||||||
],
|
],
|
||||||
"codeowners": [],
|
"codeowners": [],
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
"domain": "trend",
|
"domain": "trend",
|
||||||
"name": "Trend",
|
"name": "Trend",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/trend",
|
"documentation": "https://www.home-assistant.io/integrations/trend",
|
||||||
"requirements": ["numpy==1.21.4"],
|
"requirements": ["numpy==1.21.6"],
|
||||||
"codeowners": [],
|
"codeowners": [],
|
||||||
"quality_scale": "internal",
|
"quality_scale": "internal",
|
||||||
"iot_class": "local_push"
|
"iot_class": "local_push"
|
||||||
|
@ -7,7 +7,7 @@ from .backports.enum import StrEnum
|
|||||||
|
|
||||||
MAJOR_VERSION: Final = 2022
|
MAJOR_VERSION: Final = 2022
|
||||||
MINOR_VERSION: Final = 5
|
MINOR_VERSION: Final = 5
|
||||||
PATCH_VERSION: Final = "0"
|
PATCH_VERSION: Final = "1"
|
||||||
__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)
|
||||||
|
@ -711,6 +711,10 @@ class EntityRegistry:
|
|||||||
if not valid_entity_id(entity["entity_id"]):
|
if not valid_entity_id(entity["entity_id"]):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
# We removed this in 2022.5. Remove this check in 2023.1.
|
||||||
|
if entity["entity_category"] == "system":
|
||||||
|
entity["entity_category"] = None
|
||||||
|
|
||||||
entities[entity["entity_id"]] = RegistryEntry(
|
entities[entity["entity_id"]] = RegistryEntry(
|
||||||
area_id=entity["area_id"],
|
area_id=entity["area_id"],
|
||||||
capabilities=entity["capabilities"],
|
capabilities=entity["capabilities"],
|
||||||
|
@ -5,11 +5,13 @@ from collections.abc import Callable, Sequence
|
|||||||
from typing import Any, TypedDict, cast
|
from typing import Any, TypedDict, cast
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
import yaml
|
||||||
|
|
||||||
from homeassistant.backports.enum import StrEnum
|
from homeassistant.backports.enum import StrEnum
|
||||||
from homeassistant.const import CONF_MODE, CONF_UNIT_OF_MEASUREMENT
|
from homeassistant.const import CONF_MODE, CONF_UNIT_OF_MEASUREMENT
|
||||||
from homeassistant.core import split_entity_id, valid_entity_id
|
from homeassistant.core import split_entity_id, valid_entity_id
|
||||||
from homeassistant.util import decorator
|
from homeassistant.util import decorator
|
||||||
|
from homeassistant.util.yaml.dumper import represent_odict
|
||||||
|
|
||||||
from . import config_validation as cv
|
from . import config_validation as cv
|
||||||
|
|
||||||
@ -71,7 +73,11 @@ class Selector:
|
|||||||
|
|
||||||
def serialize(self) -> Any:
|
def serialize(self) -> Any:
|
||||||
"""Serialize Selector for voluptuous_serialize."""
|
"""Serialize Selector for voluptuous_serialize."""
|
||||||
return {"selector": {self.selector_type: self.config}}
|
return {"selector": {self.selector_type: self.serialize_config()}}
|
||||||
|
|
||||||
|
def serialize_config(self) -> Any:
|
||||||
|
"""Serialize config."""
|
||||||
|
return self.config
|
||||||
|
|
||||||
|
|
||||||
SINGLE_ENTITY_SELECTOR_CONFIG_SCHEMA = vol.Schema(
|
SINGLE_ENTITY_SELECTOR_CONFIG_SCHEMA = vol.Schema(
|
||||||
@ -623,6 +629,13 @@ class NumberSelector(Selector):
|
|||||||
"""Instantiate a selector."""
|
"""Instantiate a selector."""
|
||||||
super().__init__(config)
|
super().__init__(config)
|
||||||
|
|
||||||
|
def serialize_config(self) -> Any:
|
||||||
|
"""Serialize the selector config."""
|
||||||
|
return {
|
||||||
|
**self.config,
|
||||||
|
"mode": self.config["mode"].value,
|
||||||
|
}
|
||||||
|
|
||||||
def __call__(self, data: Any) -> float:
|
def __call__(self, data: Any) -> float:
|
||||||
"""Validate the passed selection."""
|
"""Validate the passed selection."""
|
||||||
value: float = vol.Coerce(float)(data)
|
value: float = vol.Coerce(float)(data)
|
||||||
@ -881,3 +894,11 @@ class TimeSelector(Selector):
|
|||||||
"""Validate the passed selection."""
|
"""Validate the passed selection."""
|
||||||
cv.time(data)
|
cv.time(data)
|
||||||
return cast(str, data)
|
return cast(str, data)
|
||||||
|
|
||||||
|
|
||||||
|
yaml.SafeDumper.add_representer(
|
||||||
|
Selector,
|
||||||
|
lambda dumper, value: represent_odict(
|
||||||
|
dumper, "tag:yaml.org,2002:map", value.serialize()
|
||||||
|
),
|
||||||
|
)
|
||||||
|
@ -110,7 +110,7 @@ aio_geojson_nsw_rfs_incidents==0.4
|
|||||||
aio_georss_gdacs==0.7
|
aio_georss_gdacs==0.7
|
||||||
|
|
||||||
# homeassistant.components.airzone
|
# homeassistant.components.airzone
|
||||||
aioairzone==0.4.2
|
aioairzone==0.4.3
|
||||||
|
|
||||||
# homeassistant.components.ambient_station
|
# homeassistant.components.ambient_station
|
||||||
aioambient==2021.11.0
|
aioambient==2021.11.0
|
||||||
@ -1065,7 +1065,7 @@ netdisco==3.0.0
|
|||||||
netmap==0.7.0.2
|
netmap==0.7.0.2
|
||||||
|
|
||||||
# homeassistant.components.nam
|
# homeassistant.components.nam
|
||||||
nettigo-air-monitor==1.2.2
|
nettigo-air-monitor==1.2.3
|
||||||
|
|
||||||
# homeassistant.components.neurio_energy
|
# homeassistant.components.neurio_energy
|
||||||
neurio==0.3.1
|
neurio==0.3.1
|
||||||
@ -1111,7 +1111,7 @@ numato-gpio==0.10.0
|
|||||||
# homeassistant.components.opencv
|
# homeassistant.components.opencv
|
||||||
# homeassistant.components.tensorflow
|
# homeassistant.components.tensorflow
|
||||||
# homeassistant.components.trend
|
# homeassistant.components.trend
|
||||||
numpy==1.21.4
|
numpy==1.21.6
|
||||||
|
|
||||||
# homeassistant.components.oasa_telematics
|
# homeassistant.components.oasa_telematics
|
||||||
oasatelematics==0.3
|
oasatelematics==0.3
|
||||||
@ -1399,7 +1399,7 @@ pycfdns==1.2.2
|
|||||||
pychannels==1.0.0
|
pychannels==1.0.0
|
||||||
|
|
||||||
# homeassistant.components.cast
|
# homeassistant.components.cast
|
||||||
pychromecast==12.0.0
|
pychromecast==12.1.1
|
||||||
|
|
||||||
# homeassistant.components.pocketcasts
|
# homeassistant.components.pocketcasts
|
||||||
pycketcasts==1.0.0
|
pycketcasts==1.0.0
|
||||||
|
@ -94,7 +94,7 @@ aio_geojson_nsw_rfs_incidents==0.4
|
|||||||
aio_georss_gdacs==0.7
|
aio_georss_gdacs==0.7
|
||||||
|
|
||||||
# homeassistant.components.airzone
|
# homeassistant.components.airzone
|
||||||
aioairzone==0.4.2
|
aioairzone==0.4.3
|
||||||
|
|
||||||
# homeassistant.components.ambient_station
|
# homeassistant.components.ambient_station
|
||||||
aioambient==2021.11.0
|
aioambient==2021.11.0
|
||||||
@ -727,7 +727,7 @@ netdisco==3.0.0
|
|||||||
netmap==0.7.0.2
|
netmap==0.7.0.2
|
||||||
|
|
||||||
# homeassistant.components.nam
|
# homeassistant.components.nam
|
||||||
nettigo-air-monitor==1.2.2
|
nettigo-air-monitor==1.2.3
|
||||||
|
|
||||||
# homeassistant.components.nexia
|
# homeassistant.components.nexia
|
||||||
nexia==0.9.13
|
nexia==0.9.13
|
||||||
@ -755,7 +755,7 @@ numato-gpio==0.10.0
|
|||||||
# homeassistant.components.opencv
|
# homeassistant.components.opencv
|
||||||
# homeassistant.components.tensorflow
|
# homeassistant.components.tensorflow
|
||||||
# homeassistant.components.trend
|
# homeassistant.components.trend
|
||||||
numpy==1.21.4
|
numpy==1.21.6
|
||||||
|
|
||||||
# homeassistant.components.google
|
# homeassistant.components.google
|
||||||
oauth2client==4.1.3
|
oauth2client==4.1.3
|
||||||
@ -938,7 +938,7 @@ pybotvac==0.0.23
|
|||||||
pycfdns==1.2.2
|
pycfdns==1.2.2
|
||||||
|
|
||||||
# homeassistant.components.cast
|
# homeassistant.components.cast
|
||||||
pychromecast==12.0.0
|
pychromecast==12.1.1
|
||||||
|
|
||||||
# homeassistant.components.climacell
|
# homeassistant.components.climacell
|
||||||
pyclimacell==0.18.2
|
pyclimacell==0.18.2
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[metadata]
|
[metadata]
|
||||||
name = homeassistant
|
name = homeassistant
|
||||||
version = 2022.5.0
|
version = 2022.5.1
|
||||||
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
|
||||||
|
@ -147,7 +147,7 @@ async def test_airzone_climate_turn_on_off(hass: HomeAssistant) -> None:
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.airzone.AirzoneLocalApi.http_request",
|
"homeassistant.components.airzone.AirzoneLocalApi.put_hvac",
|
||||||
return_value=HVAC_MOCK,
|
return_value=HVAC_MOCK,
|
||||||
):
|
):
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
@ -172,7 +172,7 @@ async def test_airzone_climate_turn_on_off(hass: HomeAssistant) -> None:
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.airzone.AirzoneLocalApi.http_request",
|
"homeassistant.components.airzone.AirzoneLocalApi.put_hvac",
|
||||||
return_value=HVAC_MOCK,
|
return_value=HVAC_MOCK,
|
||||||
):
|
):
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
@ -204,7 +204,7 @@ async def test_airzone_climate_set_hvac_mode(hass: HomeAssistant) -> None:
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.airzone.AirzoneLocalApi.http_request",
|
"homeassistant.components.airzone.AirzoneLocalApi.put_hvac",
|
||||||
return_value=HVAC_MOCK,
|
return_value=HVAC_MOCK,
|
||||||
):
|
):
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
@ -230,7 +230,7 @@ async def test_airzone_climate_set_hvac_mode(hass: HomeAssistant) -> None:
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.airzone.AirzoneLocalApi.http_request",
|
"homeassistant.components.airzone.AirzoneLocalApi.put_hvac",
|
||||||
return_value=HVAC_MOCK_2,
|
return_value=HVAC_MOCK_2,
|
||||||
):
|
):
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
@ -263,7 +263,7 @@ async def test_airzone_climate_set_hvac_slave_error(hass: HomeAssistant) -> None
|
|||||||
await async_init_integration(hass)
|
await async_init_integration(hass)
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.airzone.AirzoneLocalApi.http_request",
|
"homeassistant.components.airzone.AirzoneLocalApi.put_hvac",
|
||||||
return_value=HVAC_MOCK,
|
return_value=HVAC_MOCK,
|
||||||
), pytest.raises(HomeAssistantError):
|
), pytest.raises(HomeAssistantError):
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
@ -296,7 +296,7 @@ async def test_airzone_climate_set_temp(hass: HomeAssistant) -> None:
|
|||||||
await async_init_integration(hass)
|
await async_init_integration(hass)
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.airzone.AirzoneLocalApi.http_request",
|
"homeassistant.components.airzone.AirzoneLocalApi.put_hvac",
|
||||||
return_value=HVAC_MOCK,
|
return_value=HVAC_MOCK,
|
||||||
):
|
):
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
|
@ -198,7 +198,7 @@ async def test_fetch_blueprint_from_github_url(hass, aioclient_mock, url):
|
|||||||
assert imported_blueprint.blueprint.domain == "automation"
|
assert imported_blueprint.blueprint.domain == "automation"
|
||||||
assert imported_blueprint.blueprint.inputs == {
|
assert imported_blueprint.blueprint.inputs == {
|
||||||
"service_to_call": None,
|
"service_to_call": None,
|
||||||
"trigger_event": None,
|
"trigger_event": {"selector": {"text": {}}},
|
||||||
}
|
}
|
||||||
assert imported_blueprint.suggested_filename == "balloob/motion_light"
|
assert imported_blueprint.suggested_filename == "balloob/motion_light"
|
||||||
assert imported_blueprint.blueprint.metadata["source_url"] == url
|
assert imported_blueprint.blueprint.metadata["source_url"] == url
|
||||||
|
@ -30,7 +30,10 @@ async def test_list_blueprints(hass, hass_ws_client):
|
|||||||
"test_event_service.yaml": {
|
"test_event_service.yaml": {
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"domain": "automation",
|
"domain": "automation",
|
||||||
"input": {"service_to_call": None, "trigger_event": None},
|
"input": {
|
||||||
|
"service_to_call": None,
|
||||||
|
"trigger_event": {"selector": {"text": {}}},
|
||||||
|
},
|
||||||
"name": "Call service based on event",
|
"name": "Call service based on event",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -89,7 +92,10 @@ async def test_import_blueprint(hass, aioclient_mock, hass_ws_client):
|
|||||||
"blueprint": {
|
"blueprint": {
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"domain": "automation",
|
"domain": "automation",
|
||||||
"input": {"service_to_call": None, "trigger_event": None},
|
"input": {
|
||||||
|
"service_to_call": None,
|
||||||
|
"trigger_event": {"selector": {"text": {}}},
|
||||||
|
},
|
||||||
"name": "Call service based on event",
|
"name": "Call service based on event",
|
||||||
"source_url": "https://github.com/balloob/home-assistant-config/blob/main/blueprints/automation/motion_light.yaml",
|
"source_url": "https://github.com/balloob/home-assistant-config/blob/main/blueprints/automation/motion_light.yaml",
|
||||||
},
|
},
|
||||||
@ -123,7 +129,7 @@ async def test_save_blueprint(hass, aioclient_mock, hass_ws_client):
|
|||||||
assert msg["success"]
|
assert msg["success"]
|
||||||
assert write_mock.mock_calls
|
assert write_mock.mock_calls
|
||||||
assert write_mock.call_args[0] == (
|
assert write_mock.call_args[0] == (
|
||||||
"blueprint:\n name: Call service based on event\n domain: automation\n input:\n trigger_event:\n service_to_call:\n source_url: https://github.com/balloob/home-assistant-config/blob/main/blueprints/automation/motion_light.yaml\ntrigger:\n platform: event\n event_type: !input 'trigger_event'\naction:\n service: !input 'service_to_call'\n entity_id: light.kitchen\n",
|
"blueprint:\n name: Call service based on event\n domain: automation\n input:\n trigger_event:\n selector:\n text: {}\n service_to_call:\n source_url: https://github.com/balloob/home-assistant-config/blob/main/blueprints/automation/motion_light.yaml\ntrigger:\n platform: event\n event_type: !input 'trigger_event'\naction:\n service: !input 'service_to_call'\n entity_id: light.kitchen\n",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -14,6 +14,13 @@ def get_multizone_status_mock():
|
|||||||
return mock
|
return mock
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def get_cast_type_mock():
|
||||||
|
"""Mock pychromecast dial."""
|
||||||
|
mock = MagicMock(spec_set=pychromecast.dial.get_cast_type)
|
||||||
|
return mock
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture()
|
@pytest.fixture()
|
||||||
def castbrowser_mock():
|
def castbrowser_mock():
|
||||||
"""Mock pychromecast CastBrowser."""
|
"""Mock pychromecast CastBrowser."""
|
||||||
@ -43,6 +50,7 @@ def cast_mock(
|
|||||||
mz_mock,
|
mz_mock,
|
||||||
quick_play_mock,
|
quick_play_mock,
|
||||||
castbrowser_mock,
|
castbrowser_mock,
|
||||||
|
get_cast_type_mock,
|
||||||
get_chromecast_mock,
|
get_chromecast_mock,
|
||||||
get_multizone_status_mock,
|
get_multizone_status_mock,
|
||||||
):
|
):
|
||||||
@ -52,6 +60,9 @@ def cast_mock(
|
|||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.cast.discovery.pychromecast.discovery.CastBrowser",
|
"homeassistant.components.cast.discovery.pychromecast.discovery.CastBrowser",
|
||||||
castbrowser_mock,
|
castbrowser_mock,
|
||||||
|
), patch(
|
||||||
|
"homeassistant.components.cast.helpers.dial.get_cast_type",
|
||||||
|
get_cast_type_mock,
|
||||||
), patch(
|
), patch(
|
||||||
"homeassistant.components.cast.helpers.dial.get_multizone_status",
|
"homeassistant.components.cast.helpers.dial.get_multizone_status",
|
||||||
get_multizone_status_mock,
|
get_multizone_status_mock,
|
||||||
|
@ -64,6 +64,8 @@ FAKE_MDNS_SERVICE = pychromecast.discovery.ServiceInfo(
|
|||||||
pychromecast.const.SERVICE_TYPE_MDNS, "the-service"
|
pychromecast.const.SERVICE_TYPE_MDNS, "the-service"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
UNDEFINED = object()
|
||||||
|
|
||||||
|
|
||||||
def get_fake_chromecast(info: ChromecastInfo):
|
def get_fake_chromecast(info: ChromecastInfo):
|
||||||
"""Generate a Fake Chromecast object with the specified arguments."""
|
"""Generate a Fake Chromecast object with the specified arguments."""
|
||||||
@ -74,7 +76,14 @@ def get_fake_chromecast(info: ChromecastInfo):
|
|||||||
|
|
||||||
|
|
||||||
def get_fake_chromecast_info(
|
def get_fake_chromecast_info(
|
||||||
host="192.168.178.42", port=8009, service=None, uuid: UUID | None = FakeUUID
|
*,
|
||||||
|
host="192.168.178.42",
|
||||||
|
port=8009,
|
||||||
|
service=None,
|
||||||
|
uuid: UUID | None = FakeUUID,
|
||||||
|
cast_type=UNDEFINED,
|
||||||
|
manufacturer=UNDEFINED,
|
||||||
|
model_name=UNDEFINED,
|
||||||
):
|
):
|
||||||
"""Generate a Fake ChromecastInfo with the specified arguments."""
|
"""Generate a Fake ChromecastInfo with the specified arguments."""
|
||||||
|
|
||||||
@ -82,16 +91,22 @@ def get_fake_chromecast_info(
|
|||||||
service = pychromecast.discovery.ServiceInfo(
|
service = pychromecast.discovery.ServiceInfo(
|
||||||
pychromecast.const.SERVICE_TYPE_HOST, (host, port)
|
pychromecast.const.SERVICE_TYPE_HOST, (host, port)
|
||||||
)
|
)
|
||||||
|
if cast_type is UNDEFINED:
|
||||||
|
cast_type = CAST_TYPE_GROUP if port != 8009 else CAST_TYPE_CHROMECAST
|
||||||
|
if manufacturer is UNDEFINED:
|
||||||
|
manufacturer = "Nabu Casa"
|
||||||
|
if model_name is UNDEFINED:
|
||||||
|
model_name = "Chromecast"
|
||||||
return ChromecastInfo(
|
return ChromecastInfo(
|
||||||
cast_info=pychromecast.models.CastInfo(
|
cast_info=pychromecast.models.CastInfo(
|
||||||
services={service},
|
services={service},
|
||||||
uuid=uuid,
|
uuid=uuid,
|
||||||
model_name="Chromecast",
|
model_name=model_name,
|
||||||
friendly_name="Speaker",
|
friendly_name="Speaker",
|
||||||
host=host,
|
host=host,
|
||||||
port=port,
|
port=port,
|
||||||
cast_type=CAST_TYPE_GROUP if port != 8009 else CAST_TYPE_CHROMECAST,
|
cast_type=cast_type,
|
||||||
manufacturer="Nabu Casa",
|
manufacturer=manufacturer,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -342,6 +357,92 @@ async def test_internal_discovery_callback_fill_out_group(
|
|||||||
get_multizone_status_mock.assert_called_once()
|
get_multizone_status_mock.assert_called_once()
|
||||||
|
|
||||||
|
|
||||||
|
async def test_internal_discovery_callback_fill_out_cast_type_manufacturer(
|
||||||
|
hass, get_cast_type_mock, caplog
|
||||||
|
):
|
||||||
|
"""Test internal discovery automatically filling out information."""
|
||||||
|
discover_cast, _, _ = await async_setup_cast_internal_discovery(hass)
|
||||||
|
info = get_fake_chromecast_info(
|
||||||
|
host="host1",
|
||||||
|
port=8009,
|
||||||
|
service=FAKE_MDNS_SERVICE,
|
||||||
|
cast_type=None,
|
||||||
|
manufacturer=None,
|
||||||
|
)
|
||||||
|
info2 = get_fake_chromecast_info(
|
||||||
|
host="host1",
|
||||||
|
port=8009,
|
||||||
|
service=FAKE_MDNS_SERVICE,
|
||||||
|
cast_type=None,
|
||||||
|
manufacturer=None,
|
||||||
|
model_name="Model 101",
|
||||||
|
)
|
||||||
|
zconf = get_fake_zconf(host="host1", port=8009)
|
||||||
|
full_info = attr.evolve(
|
||||||
|
info,
|
||||||
|
cast_info=pychromecast.discovery.CastInfo(
|
||||||
|
services=info.cast_info.services,
|
||||||
|
uuid=FakeUUID,
|
||||||
|
model_name="Chromecast",
|
||||||
|
friendly_name="Speaker",
|
||||||
|
host=info.cast_info.host,
|
||||||
|
port=info.cast_info.port,
|
||||||
|
cast_type="audio",
|
||||||
|
manufacturer="TrollTech",
|
||||||
|
),
|
||||||
|
is_dynamic_group=None,
|
||||||
|
)
|
||||||
|
full_info2 = attr.evolve(
|
||||||
|
info2,
|
||||||
|
cast_info=pychromecast.discovery.CastInfo(
|
||||||
|
services=info.cast_info.services,
|
||||||
|
uuid=FakeUUID,
|
||||||
|
model_name="Model 101",
|
||||||
|
friendly_name="Speaker",
|
||||||
|
host=info.cast_info.host,
|
||||||
|
port=info.cast_info.port,
|
||||||
|
cast_type="cast",
|
||||||
|
manufacturer="Cyberdyne Systems",
|
||||||
|
),
|
||||||
|
is_dynamic_group=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
get_cast_type_mock.assert_not_called()
|
||||||
|
get_cast_type_mock.return_value = full_info.cast_info
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.cast.discovery.ChromeCastZeroconf.get_zeroconf",
|
||||||
|
return_value=zconf,
|
||||||
|
):
|
||||||
|
signal = MagicMock()
|
||||||
|
|
||||||
|
async_dispatcher_connect(hass, "cast_discovered", signal)
|
||||||
|
discover_cast(FAKE_MDNS_SERVICE, info)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
# when called with incomplete info, it should use HTTP to get missing
|
||||||
|
get_cast_type_mock.assert_called_once()
|
||||||
|
assert get_cast_type_mock.call_count == 1
|
||||||
|
discover = signal.mock_calls[0][1][0]
|
||||||
|
assert discover == full_info
|
||||||
|
assert "Fetched cast details for unknown model 'Chromecast'" in caplog.text
|
||||||
|
|
||||||
|
# Call again, the model name should be fetched from cache
|
||||||
|
discover_cast(FAKE_MDNS_SERVICE, info)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert get_cast_type_mock.call_count == 1 # No additional calls
|
||||||
|
discover = signal.mock_calls[1][1][0]
|
||||||
|
assert discover == full_info
|
||||||
|
|
||||||
|
# Call for another model, need to call HTTP again
|
||||||
|
get_cast_type_mock.return_value = full_info2.cast_info
|
||||||
|
discover_cast(FAKE_MDNS_SERVICE, info2)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert get_cast_type_mock.call_count == 2
|
||||||
|
discover = signal.mock_calls[2][1][0]
|
||||||
|
assert discover == full_info2
|
||||||
|
|
||||||
|
|
||||||
async def test_stop_discovery_called_on_stop(hass, castbrowser_mock):
|
async def test_stop_discovery_called_on_stop(hass, castbrowser_mock):
|
||||||
"""Test pychromecast.stop_discovery called on shutdown."""
|
"""Test pychromecast.stop_discovery called on shutdown."""
|
||||||
# start_discovery should be called with empty config
|
# start_discovery should be called with empty config
|
||||||
|
@ -22,7 +22,7 @@ from samsungtvws.remote import ChannelEmitCommand
|
|||||||
from homeassistant.components.samsungtv.const import WEBSOCKET_SSL_PORT
|
from homeassistant.components.samsungtv.const import WEBSOCKET_SSL_PORT
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
|
|
||||||
from .const import SAMPLE_DEVICE_INFO_WIFI
|
from .const import SAMPLE_DEVICE_INFO_UE48JU6400, SAMPLE_DEVICE_INFO_WIFI
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
@ -177,7 +177,7 @@ def rest_api_fixture_non_ssl_only() -> Mock:
|
|||||||
"""Mock rest_device_info to fail for ssl and work for non-ssl."""
|
"""Mock rest_device_info to fail for ssl and work for non-ssl."""
|
||||||
if self.port == WEBSOCKET_SSL_PORT:
|
if self.port == WEBSOCKET_SSL_PORT:
|
||||||
raise ResponseError
|
raise ResponseError
|
||||||
return SAMPLE_DEVICE_INFO_WIFI
|
return SAMPLE_DEVICE_INFO_UE48JU6400
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.samsungtv.bridge.SamsungTVAsyncRest",
|
"homeassistant.components.samsungtv.bridge.SamsungTVAsyncRest",
|
||||||
|
@ -340,16 +340,16 @@ async def test_user_encrypted_websocket(
|
|||||||
)
|
)
|
||||||
|
|
||||||
assert result4["type"] == "create_entry"
|
assert result4["type"] == "create_entry"
|
||||||
assert result4["title"] == "Living Room (82GXARRS)"
|
assert result4["title"] == "TV-UE48JU6470 (UE48JU6400)"
|
||||||
assert result4["data"][CONF_HOST] == "fake_host"
|
assert result4["data"][CONF_HOST] == "fake_host"
|
||||||
assert result4["data"][CONF_NAME] == "Living Room"
|
assert result4["data"][CONF_NAME] == "TV-UE48JU6470"
|
||||||
assert result4["data"][CONF_MAC] == "aa:bb:ww:ii:ff:ii"
|
assert result4["data"][CONF_MAC] == "aa:bb:ww:ii:ff:ii"
|
||||||
assert result4["data"][CONF_MANUFACTURER] == "Samsung"
|
assert result4["data"][CONF_MANUFACTURER] == "Samsung"
|
||||||
assert result4["data"][CONF_MODEL] == "82GXARRS"
|
assert result4["data"][CONF_MODEL] == "UE48JU6400"
|
||||||
assert result4["data"][CONF_SSDP_RENDERING_CONTROL_LOCATION] is None
|
assert result4["data"][CONF_SSDP_RENDERING_CONTROL_LOCATION] is None
|
||||||
assert result4["data"][CONF_TOKEN] == "037739871315caef138547b03e348b72"
|
assert result4["data"][CONF_TOKEN] == "037739871315caef138547b03e348b72"
|
||||||
assert result4["data"][CONF_SESSION_ID] == "1"
|
assert result4["data"][CONF_SESSION_ID] == "1"
|
||||||
assert result4["result"].unique_id == "be9554b9-c9fb-41f4-8920-22da015376a4"
|
assert result4["result"].unique_id == "223da676-497a-4e06-9507-5e27ec4f0fb3"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("rest_api_failing")
|
@pytest.mark.usefixtures("rest_api_failing")
|
||||||
@ -714,19 +714,19 @@ async def test_ssdp_encrypted_websocket_success_populates_mac_address_and_ssdp_l
|
|||||||
)
|
)
|
||||||
|
|
||||||
assert result4["type"] == "create_entry"
|
assert result4["type"] == "create_entry"
|
||||||
assert result4["title"] == "Living Room (82GXARRS)"
|
assert result4["title"] == "TV-UE48JU6470 (UE48JU6400)"
|
||||||
assert result4["data"][CONF_HOST] == "fake_host"
|
assert result4["data"][CONF_HOST] == "fake_host"
|
||||||
assert result4["data"][CONF_NAME] == "Living Room"
|
assert result4["data"][CONF_NAME] == "TV-UE48JU6470"
|
||||||
assert result4["data"][CONF_MAC] == "aa:bb:ww:ii:ff:ii"
|
assert result4["data"][CONF_MAC] == "aa:bb:ww:ii:ff:ii"
|
||||||
assert result4["data"][CONF_MANUFACTURER] == "Samsung fake_manufacturer"
|
assert result4["data"][CONF_MANUFACTURER] == "Samsung fake_manufacturer"
|
||||||
assert result4["data"][CONF_MODEL] == "82GXARRS"
|
assert result4["data"][CONF_MODEL] == "UE48JU6400"
|
||||||
assert (
|
assert (
|
||||||
result4["data"][CONF_SSDP_RENDERING_CONTROL_LOCATION]
|
result4["data"][CONF_SSDP_RENDERING_CONTROL_LOCATION]
|
||||||
== "https://fake_host:12345/test"
|
== "https://fake_host:12345/test"
|
||||||
)
|
)
|
||||||
assert result4["data"][CONF_TOKEN] == "037739871315caef138547b03e348b72"
|
assert result4["data"][CONF_TOKEN] == "037739871315caef138547b03e348b72"
|
||||||
assert result4["data"][CONF_SESSION_ID] == "1"
|
assert result4["data"][CONF_SESSION_ID] == "1"
|
||||||
assert result4["result"].unique_id == "be9554b9-c9fb-41f4-8920-22da015376a4"
|
assert result4["result"].unique_id == "223da676-497a-4e06-9507-5e27ec4f0fb3"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("rest_api_non_ssl_only")
|
@pytest.mark.usefixtures("rest_api_non_ssl_only")
|
||||||
@ -1036,13 +1036,13 @@ async def test_dhcp_wireless(hass: HomeAssistant) -> None:
|
|||||||
result["flow_id"], user_input="whatever"
|
result["flow_id"], user_input="whatever"
|
||||||
)
|
)
|
||||||
assert result["type"] == "create_entry"
|
assert result["type"] == "create_entry"
|
||||||
assert result["title"] == "Living Room (82GXARRS)"
|
assert result["title"] == "TV-UE48JU6470 (UE48JU6400)"
|
||||||
assert result["data"][CONF_HOST] == "fake_host"
|
assert result["data"][CONF_HOST] == "fake_host"
|
||||||
assert result["data"][CONF_NAME] == "Living Room"
|
assert result["data"][CONF_NAME] == "TV-UE48JU6470"
|
||||||
assert result["data"][CONF_MAC] == "aa:bb:ww:ii:ff:ii"
|
assert result["data"][CONF_MAC] == "aa:bb:ww:ii:ff:ii"
|
||||||
assert result["data"][CONF_MANUFACTURER] == "Samsung"
|
assert result["data"][CONF_MANUFACTURER] == "Samsung"
|
||||||
assert result["data"][CONF_MODEL] == "82GXARRS"
|
assert result["data"][CONF_MODEL] == "UE48JU6400"
|
||||||
assert result["result"].unique_id == "be9554b9-c9fb-41f4-8920-22da015376a4"
|
assert result["result"].unique_id == "223da676-497a-4e06-9507-5e27ec4f0fb3"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("remotews", "rest_api", "remoteencws_failing")
|
@pytest.mark.usefixtures("remotews", "rest_api", "remoteencws_failing")
|
||||||
|
@ -11,7 +11,7 @@ import pytest
|
|||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
import homeassistant
|
import homeassistant
|
||||||
from homeassistant.helpers import config_validation as cv, template
|
from homeassistant.helpers import config_validation as cv, selector, template
|
||||||
|
|
||||||
|
|
||||||
def test_boolean():
|
def test_boolean():
|
||||||
@ -720,6 +720,17 @@ def test_string_in_serializer():
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_selector_in_serializer():
|
||||||
|
"""Test selector with custom_serializer."""
|
||||||
|
assert cv.custom_serializer(selector.selector({"text": {}})) == {
|
||||||
|
"selector": {
|
||||||
|
"text": {
|
||||||
|
"multiline": False,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def test_positive_time_period_dict_in_serializer():
|
def test_positive_time_period_dict_in_serializer():
|
||||||
"""Test positive_time_period_dict with custom_serializer."""
|
"""Test positive_time_period_dict with custom_serializer."""
|
||||||
assert cv.custom_serializer(cv.positive_time_period_dict) == {
|
assert cv.custom_serializer(cv.positive_time_period_dict) == {
|
||||||
|
@ -297,6 +297,12 @@ async def test_loading_extra_values(hass, hass_storage):
|
|||||||
"unique_id": "invalid-hass",
|
"unique_id": "invalid-hass",
|
||||||
"disabled_by": er.RegistryEntryDisabler.HASS,
|
"disabled_by": er.RegistryEntryDisabler.HASS,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"entity_id": "test.system_entity",
|
||||||
|
"platform": "super_platform",
|
||||||
|
"unique_id": "system-entity",
|
||||||
|
"entity_category": "system",
|
||||||
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -304,7 +310,7 @@ async def test_loading_extra_values(hass, hass_storage):
|
|||||||
await er.async_load(hass)
|
await er.async_load(hass)
|
||||||
registry = er.async_get(hass)
|
registry = er.async_get(hass)
|
||||||
|
|
||||||
assert len(registry.entities) == 4
|
assert len(registry.entities) == 5
|
||||||
|
|
||||||
entry_with_name = registry.async_get_or_create(
|
entry_with_name = registry.async_get_or_create(
|
||||||
"test", "super_platform", "with-name"
|
"test", "super_platform", "with-name"
|
||||||
@ -327,6 +333,11 @@ async def test_loading_extra_values(hass, hass_storage):
|
|||||||
assert entry_disabled_user.disabled
|
assert entry_disabled_user.disabled
|
||||||
assert entry_disabled_user.disabled_by is er.RegistryEntryDisabler.USER
|
assert entry_disabled_user.disabled_by is er.RegistryEntryDisabler.USER
|
||||||
|
|
||||||
|
entry_system_category = registry.async_get_or_create(
|
||||||
|
"test", "system_entity", "system-entity"
|
||||||
|
)
|
||||||
|
assert entry_system_category.entity_category is None
|
||||||
|
|
||||||
|
|
||||||
def test_async_get_entity_id(registry):
|
def test_async_get_entity_id(registry):
|
||||||
"""Test that entity_id is returned."""
|
"""Test that entity_id is returned."""
|
||||||
|
@ -2,7 +2,8 @@
|
|||||||
import pytest
|
import pytest
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.helpers import config_validation as cv, selector
|
from homeassistant.helpers import selector
|
||||||
|
from homeassistant.util import yaml
|
||||||
|
|
||||||
FAKE_UUID = "a266a680b608c32770e6c45bfe6b8411"
|
FAKE_UUID = "a266a680b608c32770e6c45bfe6b8411"
|
||||||
|
|
||||||
@ -48,10 +49,12 @@ def _test_selector(
|
|||||||
converter = default_converter
|
converter = default_converter
|
||||||
|
|
||||||
# Validate selector configuration
|
# Validate selector configuration
|
||||||
selector.validate_selector({selector_type: schema})
|
config = {selector_type: schema}
|
||||||
|
selector.validate_selector(config)
|
||||||
|
selector_instance = selector.selector(config)
|
||||||
|
|
||||||
# Use selector in schema and validate
|
# Use selector in schema and validate
|
||||||
vol_schema = vol.Schema({"selection": selector.selector({selector_type: schema})})
|
vol_schema = vol.Schema({"selection": selector_instance})
|
||||||
for selection in valid_selections:
|
for selection in valid_selections:
|
||||||
assert vol_schema({"selection": selection}) == {
|
assert vol_schema({"selection": selection}) == {
|
||||||
"selection": converter(selection)
|
"selection": converter(selection)
|
||||||
@ -62,9 +65,12 @@ def _test_selector(
|
|||||||
|
|
||||||
# Serialize selector
|
# Serialize selector
|
||||||
selector_instance = selector.selector({selector_type: schema})
|
selector_instance = selector.selector({selector_type: schema})
|
||||||
assert cv.custom_serializer(selector_instance) == {
|
assert (
|
||||||
"selector": {selector_type: selector_instance.config}
|
selector.selector(selector_instance.serialize()["selector"]).config
|
||||||
}
|
== selector_instance.config
|
||||||
|
)
|
||||||
|
# Test serialized selector can be dumped to YAML
|
||||||
|
yaml.dump(selector_instance.serialize())
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
|
@ -3,6 +3,8 @@ blueprint:
|
|||||||
domain: automation
|
domain: automation
|
||||||
input:
|
input:
|
||||||
trigger_event:
|
trigger_event:
|
||||||
|
selector:
|
||||||
|
text:
|
||||||
service_to_call:
|
service_to_call:
|
||||||
trigger:
|
trigger:
|
||||||
platform: event
|
platform: event
|
||||||
|
Loading…
x
Reference in New Issue
Block a user