mirror of
https://github.com/home-assistant/core.git
synced 2025-10-03 16:59:30 +00:00
Compare commits
16 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
111a00aeeb | ||
![]() |
b3d5717df8 | ||
![]() |
a84378bb79 | ||
![]() |
53ba45cc8f | ||
![]() |
5d77eb1839 | ||
![]() |
517159dc4e | ||
![]() |
a9287b7117 | ||
![]() |
0b7bcc87df | ||
![]() |
482661f82c | ||
![]() |
865d65c380 | ||
![]() |
609d202c4d | ||
![]() |
e43a0087e4 | ||
![]() |
b9bc147339 | ||
![]() |
dd3b0df22d | ||
![]() |
c987ca735e | ||
![]() |
4b1761ccb5 |
@@ -40,6 +40,16 @@ def async_setup(hass):
|
||||
hass.http.register_view(AlexaIntentsView)
|
||||
|
||||
|
||||
async def async_setup_intents(hass):
|
||||
"""
|
||||
Do intents setup.
|
||||
|
||||
Right now this module does not expose any, but the intent component breaks
|
||||
without it.
|
||||
"""
|
||||
pass # pylint: disable=unnecessary-pass
|
||||
|
||||
|
||||
class UnknownRequest(HomeAssistantError):
|
||||
"""When an unknown Alexa request is passed in."""
|
||||
|
||||
|
@@ -3,7 +3,6 @@ import logging
|
||||
import threading
|
||||
|
||||
import pychromecast
|
||||
import zeroconf
|
||||
|
||||
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
|
||||
from homeassistant.core import HomeAssistant
|
||||
@@ -85,14 +84,12 @@ def setup_internal_discovery(hass: HomeAssistant) -> None:
|
||||
)
|
||||
|
||||
_LOGGER.debug("Starting internal pychromecast discovery.")
|
||||
listener = pychromecast.discovery.CastListener(
|
||||
internal_add_callback, internal_remove_callback
|
||||
)
|
||||
browser = zeroconf.ServiceBrowser(
|
||||
ChromeCastZeroconf.get_zeroconf() or zeroconf.Zeroconf(),
|
||||
"_googlecast._tcp.local.",
|
||||
listener,
|
||||
listener = pychromecast.CastListener(
|
||||
internal_add_callback,
|
||||
internal_remove_callback,
|
||||
internal_add_callback, # Use internal_add_callback also for updates
|
||||
)
|
||||
browser = pychromecast.start_discovery(listener, ChromeCastZeroconf.get_zeroconf())
|
||||
|
||||
def stop_discovery(event):
|
||||
"""Stop discovery of new chromecasts."""
|
||||
|
@@ -3,7 +3,7 @@
|
||||
"name": "Google Cast",
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/cast",
|
||||
"requirements": ["pychromecast==5.3.0"],
|
||||
"requirements": ["pychromecast==6.0.0"],
|
||||
"after_dependencies": ["cloud","zeroconf"],
|
||||
"zeroconf": ["_googlecast._tcp.local."],
|
||||
"codeowners": ["@emontnemery"]
|
||||
|
@@ -2,7 +2,7 @@
|
||||
"domain": "discovery",
|
||||
"name": "Discovery",
|
||||
"documentation": "https://www.home-assistant.io/integrations/discovery",
|
||||
"requirements": ["netdisco==2.6.0"],
|
||||
"requirements": ["netdisco==2.7.0"],
|
||||
"after_dependencies": ["zeroconf"],
|
||||
"codeowners": [],
|
||||
"quality_scale": "internal"
|
||||
|
@@ -2,7 +2,7 @@
|
||||
"domain": "frontend",
|
||||
"name": "Home Assistant Frontend",
|
||||
"documentation": "https://www.home-assistant.io/integrations/frontend",
|
||||
"requirements": ["home-assistant-frontend==20200603.1"],
|
||||
"requirements": ["home-assistant-frontend==20200603.2"],
|
||||
"dependencies": [
|
||||
"api",
|
||||
"auth",
|
||||
|
@@ -232,7 +232,7 @@ NODE_FILTERS = {
|
||||
"RemoteLinc2_ADV",
|
||||
],
|
||||
FILTER_INSTEON_TYPE: ["0.16.", "0.17.", "0.18.", "9.0.", "9.7."],
|
||||
FILTER_ZWAVE_CAT: (["118", "143"] + list(map(str, range(180, 185)))),
|
||||
FILTER_ZWAVE_CAT: (["118", "143"] + list(map(str, range(180, 186)))),
|
||||
},
|
||||
LOCK: {
|
||||
FILTER_UOM: ["11"],
|
||||
|
@@ -56,7 +56,6 @@ ERR_ENCRYPTION_ALREADY_ENABLED = "encryption_already_enabled"
|
||||
ERR_ENCRYPTION_NOT_AVAILABLE = "encryption_not_available"
|
||||
ERR_ENCRYPTION_REQUIRED = "encryption_required"
|
||||
ERR_SENSOR_NOT_REGISTERED = "not_registered"
|
||||
ERR_SENSOR_DUPLICATE_UNIQUE_ID = "duplicate_unique_id"
|
||||
ERR_INVALID_FORMAT = "invalid_format"
|
||||
|
||||
|
||||
|
@@ -1,4 +1,5 @@
|
||||
"""Webhook handlers for mobile_app."""
|
||||
import asyncio
|
||||
from functools import wraps
|
||||
import logging
|
||||
import secrets
|
||||
@@ -28,7 +29,7 @@ from homeassistant.const import (
|
||||
HTTP_CREATED,
|
||||
)
|
||||
from homeassistant.core import EventOrigin
|
||||
from homeassistant.exceptions import HomeAssistantError, ServiceNotFound, TemplateError
|
||||
from homeassistant.exceptions import ServiceNotFound, TemplateError
|
||||
from homeassistant.helpers import config_validation as cv, device_registry as dr
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||
from homeassistant.helpers.template import attach
|
||||
@@ -77,7 +78,6 @@ from .const import (
|
||||
ERR_ENCRYPTION_NOT_AVAILABLE,
|
||||
ERR_ENCRYPTION_REQUIRED,
|
||||
ERR_INVALID_FORMAT,
|
||||
ERR_SENSOR_DUPLICATE_UNIQUE_ID,
|
||||
ERR_SENSOR_NOT_REGISTERED,
|
||||
SIGNAL_LOCATION_UPDATE,
|
||||
SIGNAL_SENSOR_UPDATE,
|
||||
@@ -95,6 +95,7 @@ from .helpers import (
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DELAY_SAVE = 10
|
||||
|
||||
WEBHOOK_COMMANDS = Registry()
|
||||
|
||||
@@ -184,7 +185,10 @@ async def handle_webhook(
|
||||
"Received webhook payload for type %s: %s", webhook_type, webhook_payload
|
||||
)
|
||||
|
||||
return await WEBHOOK_COMMANDS[webhook_type](hass, config_entry, webhook_payload)
|
||||
# Shield so we make sure we finish the webhook, even if sender hangs up.
|
||||
return await asyncio.shield(
|
||||
WEBHOOK_COMMANDS[webhook_type](hass, config_entry, webhook_payload)
|
||||
)
|
||||
|
||||
|
||||
@WEBHOOK_COMMANDS.register("call_service")
|
||||
@@ -352,38 +356,37 @@ async def webhook_enable_encryption(hass, config_entry, data):
|
||||
vol.Required(ATTR_SENSOR_TYPE): vol.In(SENSOR_TYPES),
|
||||
vol.Required(ATTR_SENSOR_UNIQUE_ID): cv.string,
|
||||
vol.Optional(ATTR_SENSOR_UOM): cv.string,
|
||||
vol.Required(ATTR_SENSOR_STATE): vol.Any(bool, str, int, float),
|
||||
vol.Required(ATTR_SENSOR_STATE): vol.Any(None, bool, str, int, float),
|
||||
vol.Optional(ATTR_SENSOR_ICON, default="mdi:cellphone"): cv.icon,
|
||||
}
|
||||
)
|
||||
async def webhook_register_sensor(hass, config_entry, data):
|
||||
"""Handle a register sensor webhook."""
|
||||
entity_type = data[ATTR_SENSOR_TYPE]
|
||||
|
||||
unique_id = data[ATTR_SENSOR_UNIQUE_ID]
|
||||
|
||||
unique_store_key = f"{config_entry.data[CONF_WEBHOOK_ID]}_{unique_id}"
|
||||
|
||||
if unique_store_key in hass.data[DOMAIN][entity_type]:
|
||||
_LOGGER.error("Refusing to re-register existing sensor %s!", unique_id)
|
||||
return error_response(
|
||||
ERR_SENSOR_DUPLICATE_UNIQUE_ID,
|
||||
f"{entity_type} {unique_id} already exists!",
|
||||
status=409,
|
||||
)
|
||||
existing_sensor = unique_store_key in hass.data[DOMAIN][entity_type]
|
||||
|
||||
data[CONF_WEBHOOK_ID] = config_entry.data[CONF_WEBHOOK_ID]
|
||||
|
||||
# If sensor already is registered, update current state instead
|
||||
if existing_sensor:
|
||||
_LOGGER.debug("Re-register existing sensor %s", unique_id)
|
||||
entry = hass.data[DOMAIN][entity_type][unique_store_key]
|
||||
data = {**entry, **data}
|
||||
|
||||
hass.data[DOMAIN][entity_type][unique_store_key] = data
|
||||
|
||||
try:
|
||||
await hass.data[DOMAIN][DATA_STORE].async_save(savable_state(hass))
|
||||
except HomeAssistantError as ex:
|
||||
_LOGGER.error("Error registering sensor: %s", ex)
|
||||
return empty_okay_response()
|
||||
hass.data[DOMAIN][DATA_STORE].async_delay_save(
|
||||
lambda: savable_state(hass), DELAY_SAVE
|
||||
)
|
||||
|
||||
register_signal = f"{DOMAIN}_{data[ATTR_SENSOR_TYPE]}_register"
|
||||
async_dispatcher_send(hass, register_signal, data)
|
||||
if existing_sensor:
|
||||
async_dispatcher_send(hass, SIGNAL_SENSOR_UPDATE, data)
|
||||
else:
|
||||
register_signal = f"{DOMAIN}_{data[ATTR_SENSOR_TYPE]}_register"
|
||||
async_dispatcher_send(hass, register_signal, data)
|
||||
|
||||
return webhook_response(
|
||||
{"success": True}, registration=config_entry.data, status=HTTP_CREATED,
|
||||
@@ -414,7 +417,7 @@ async def webhook_update_sensor_states(hass, config_entry, data):
|
||||
{
|
||||
vol.Optional(ATTR_SENSOR_ATTRIBUTES, default={}): dict,
|
||||
vol.Optional(ATTR_SENSOR_ICON, default="mdi:cellphone"): cv.icon,
|
||||
vol.Required(ATTR_SENSOR_STATE): vol.Any(bool, str, int, float),
|
||||
vol.Required(ATTR_SENSOR_STATE): vol.Any(None, bool, str, int, float),
|
||||
vol.Required(ATTR_SENSOR_TYPE): vol.In(SENSOR_TYPES),
|
||||
vol.Required(ATTR_SENSOR_UNIQUE_ID): cv.string,
|
||||
}
|
||||
@@ -458,18 +461,14 @@ async def webhook_update_sensor_states(hass, config_entry, data):
|
||||
|
||||
hass.data[DOMAIN][entity_type][unique_store_key] = new_state
|
||||
|
||||
safe = savable_state(hass)
|
||||
|
||||
try:
|
||||
await hass.data[DOMAIN][DATA_STORE].async_save(safe)
|
||||
except HomeAssistantError as ex:
|
||||
_LOGGER.error("Error updating mobile_app registration: %s", ex)
|
||||
return empty_okay_response()
|
||||
|
||||
async_dispatcher_send(hass, SIGNAL_SENSOR_UPDATE, new_state)
|
||||
|
||||
resp[unique_id] = {"success": True}
|
||||
|
||||
hass.data[DOMAIN][DATA_STORE].async_delay_save(
|
||||
lambda: savable_state(hass), DELAY_SAVE
|
||||
)
|
||||
|
||||
return webhook_response(resp, registration=config_entry.data)
|
||||
|
||||
|
||||
|
@@ -2,7 +2,7 @@
|
||||
"domain": "ssdp",
|
||||
"name": "Simple Service Discovery Protocol (SSDP)",
|
||||
"documentation": "https://www.home-assistant.io/integrations/ssdp",
|
||||
"requirements": ["defusedxml==0.6.0", "netdisco==2.6.0"],
|
||||
"requirements": ["defusedxml==0.6.0", "netdisco==2.7.0"],
|
||||
"after_dependencies": ["zeroconf"],
|
||||
"codeowners": []
|
||||
}
|
||||
|
@@ -83,20 +83,78 @@ async def async_setup_entry(
|
||||
update_segments()
|
||||
|
||||
|
||||
class WLEDLight(LightEntity, WLEDDeviceEntity):
|
||||
"""Defines a WLED light."""
|
||||
class WLEDMasterLight(LightEntity, WLEDDeviceEntity):
|
||||
"""Defines a WLED master light."""
|
||||
|
||||
def __init__(self, entry_id: str, coordinator: WLEDDataUpdateCoordinator):
|
||||
"""Initialize WLED master light."""
|
||||
super().__init__(
|
||||
entry_id=entry_id,
|
||||
coordinator=coordinator,
|
||||
name=f"{coordinator.data.info.name} Master",
|
||||
icon="mdi:led-strip-variant",
|
||||
)
|
||||
|
||||
@property
|
||||
def unique_id(self) -> str:
|
||||
"""Return the unique ID for this sensor."""
|
||||
return f"{self.coordinator.data.info.mac_address}"
|
||||
|
||||
@property
|
||||
def supported_features(self) -> int:
|
||||
"""Flag supported features."""
|
||||
return SUPPORT_BRIGHTNESS | SUPPORT_TRANSITION
|
||||
|
||||
@property
|
||||
def brightness(self) -> Optional[int]:
|
||||
"""Return the brightness of this light between 1..255."""
|
||||
return self.coordinator.data.state.brightness
|
||||
|
||||
@property
|
||||
def is_on(self) -> bool:
|
||||
"""Return the state of the light."""
|
||||
return bool(self.coordinator.data.state.on)
|
||||
|
||||
@wled_exception_handler
|
||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||
"""Turn off the light."""
|
||||
data = {ATTR_ON: False}
|
||||
|
||||
if ATTR_TRANSITION in kwargs:
|
||||
# WLED uses 100ms per unit, so 10 = 1 second.
|
||||
data[ATTR_TRANSITION] = round(kwargs[ATTR_TRANSITION] * 10)
|
||||
|
||||
await self.coordinator.wled.master(**data)
|
||||
|
||||
@wled_exception_handler
|
||||
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||
"""Turn on the light."""
|
||||
data = {ATTR_ON: True}
|
||||
|
||||
if ATTR_TRANSITION in kwargs:
|
||||
# WLED uses 100ms per unit, so 10 = 1 second.
|
||||
data[ATTR_TRANSITION] = round(kwargs[ATTR_TRANSITION] * 10)
|
||||
|
||||
if ATTR_BRIGHTNESS in kwargs:
|
||||
data[ATTR_BRIGHTNESS] = kwargs[ATTR_BRIGHTNESS]
|
||||
|
||||
await self.coordinator.wled.master(**data)
|
||||
|
||||
|
||||
class WLEDSegmentLight(LightEntity, WLEDDeviceEntity):
|
||||
"""Defines a WLED light based on a segment."""
|
||||
|
||||
def __init__(
|
||||
self, entry_id: str, coordinator: WLEDDataUpdateCoordinator, segment: int
|
||||
):
|
||||
"""Initialize WLED light."""
|
||||
"""Initialize WLED segment light."""
|
||||
self._rgbw = coordinator.data.info.leds.rgbw
|
||||
self._segment = segment
|
||||
|
||||
# Only apply the segment ID if it is not the first segment
|
||||
name = coordinator.data.info.name
|
||||
if segment != 0:
|
||||
name += f" {segment}"
|
||||
# If this is the one and only segment, use a simpler name
|
||||
name = f"{coordinator.data.info.name} Segment {self._segment}"
|
||||
if len(coordinator.data.state.segments) == 1:
|
||||
name = coordinator.data.info.name
|
||||
|
||||
super().__init__(
|
||||
entry_id=entry_id,
|
||||
@@ -155,7 +213,16 @@ class WLEDLight(LightEntity, WLEDDeviceEntity):
|
||||
@property
|
||||
def brightness(self) -> Optional[int]:
|
||||
"""Return the brightness of this light between 1..255."""
|
||||
return self.coordinator.data.state.brightness
|
||||
state = self.coordinator.data.state
|
||||
|
||||
# If this is the one and only segment, calculate brightness based
|
||||
# on the master and segment brightness
|
||||
if len(state.segments) == 1:
|
||||
return int(
|
||||
(state.segments[self._segment].brightness * state.brightness) / 255
|
||||
)
|
||||
|
||||
return state.segments[self._segment].brightness
|
||||
|
||||
@property
|
||||
def white_value(self) -> Optional[int]:
|
||||
@@ -187,18 +254,30 @@ class WLEDLight(LightEntity, WLEDDeviceEntity):
|
||||
@property
|
||||
def is_on(self) -> bool:
|
||||
"""Return the state of the light."""
|
||||
return bool(self.coordinator.data.state.on)
|
||||
state = self.coordinator.data.state
|
||||
|
||||
# If there is a single segment, take master into account
|
||||
if len(state.segments) == 1 and not state.on:
|
||||
return False
|
||||
|
||||
return bool(state.segments[self._segment].on)
|
||||
|
||||
@wled_exception_handler
|
||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||
"""Turn off the light."""
|
||||
data = {ATTR_ON: False, ATTR_SEGMENT_ID: self._segment}
|
||||
data = {ATTR_ON: False}
|
||||
|
||||
if ATTR_TRANSITION in kwargs:
|
||||
# WLED uses 100ms per unit, so 10 = 1 second.
|
||||
data[ATTR_TRANSITION] = round(kwargs[ATTR_TRANSITION] * 10)
|
||||
|
||||
await self.coordinator.wled.light(**data)
|
||||
# If there is a single segment, control via the master
|
||||
if len(self.coordinator.data.state.segments) == 1:
|
||||
await self.coordinator.wled.master(**data)
|
||||
return
|
||||
|
||||
data[ATTR_SEGMENT_ID] = self._segment
|
||||
await self.coordinator.wled.segment(**data)
|
||||
|
||||
@wled_exception_handler
|
||||
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||
@@ -248,7 +327,23 @@ class WLEDLight(LightEntity, WLEDDeviceEntity):
|
||||
else:
|
||||
data[ATTR_COLOR_PRIMARY] += (self.white_value,)
|
||||
|
||||
await self.coordinator.wled.light(**data)
|
||||
# When only 1 segment is present, switch along the master, and use
|
||||
# the master for power/brightness control.
|
||||
if len(self.coordinator.data.state.segments) == 1:
|
||||
master_data = {ATTR_ON: True}
|
||||
if ATTR_BRIGHTNESS in data:
|
||||
master_data[ATTR_BRIGHTNESS] = data[ATTR_BRIGHTNESS]
|
||||
data[ATTR_BRIGHTNESS] = 255
|
||||
|
||||
if ATTR_TRANSITION in data:
|
||||
master_data[ATTR_TRANSITION] = data[ATTR_TRANSITION]
|
||||
del data[ATTR_TRANSITION]
|
||||
|
||||
await self.coordinator.wled.segment(**data)
|
||||
await self.coordinator.wled.master(**master_data)
|
||||
return
|
||||
|
||||
await self.coordinator.wled.segment(**data)
|
||||
|
||||
@wled_exception_handler
|
||||
async def async_effect(
|
||||
@@ -273,45 +368,59 @@ class WLEDLight(LightEntity, WLEDDeviceEntity):
|
||||
if speed is not None:
|
||||
data[ATTR_SPEED] = speed
|
||||
|
||||
await self.coordinator.wled.light(**data)
|
||||
await self.coordinator.wled.segment(**data)
|
||||
|
||||
|
||||
@callback
|
||||
def async_update_segments(
|
||||
entry: ConfigEntry,
|
||||
coordinator: WLEDDataUpdateCoordinator,
|
||||
current: Dict[int, WLEDLight],
|
||||
current: Dict[int, WLEDSegmentLight],
|
||||
async_add_entities,
|
||||
) -> None:
|
||||
"""Update segments."""
|
||||
segment_ids = {light.segment_id for light in coordinator.data.state.segments}
|
||||
current_ids = set(current)
|
||||
|
||||
# Process new segments, add them to Home Assistant
|
||||
new_segments = []
|
||||
for segment_id in segment_ids - current_ids:
|
||||
current[segment_id] = WLEDLight(entry.entry_id, coordinator, segment_id)
|
||||
new_segments.append(current[segment_id])
|
||||
# Discard master (if present)
|
||||
current_ids.discard(-1)
|
||||
|
||||
if new_segments:
|
||||
async_add_entities(new_segments)
|
||||
# Process new segments, add them to Home Assistant
|
||||
new_entities = []
|
||||
for segment_id in segment_ids - current_ids:
|
||||
current[segment_id] = WLEDSegmentLight(entry.entry_id, coordinator, segment_id)
|
||||
new_entities.append(current[segment_id])
|
||||
|
||||
# More than 1 segment now? Add master controls
|
||||
if len(current_ids) < 2 and len(segment_ids) > 1:
|
||||
current[-1] = WLEDMasterLight(entry.entry_id, coordinator)
|
||||
new_entities.append(current[-1])
|
||||
|
||||
if new_entities:
|
||||
async_add_entities(new_entities)
|
||||
|
||||
# Process deleted segments, remove them from Home Assistant
|
||||
for segment_id in current_ids - segment_ids:
|
||||
coordinator.hass.async_create_task(
|
||||
async_remove_segment(segment_id, coordinator, current)
|
||||
async_remove_entity(segment_id, coordinator, current)
|
||||
)
|
||||
|
||||
# Remove master if there is only 1 segment left
|
||||
if len(current_ids) > 1 and len(segment_ids) < 2:
|
||||
coordinator.hass.async_create_task(
|
||||
async_remove_entity(-1, coordinator, current)
|
||||
)
|
||||
|
||||
|
||||
async def async_remove_segment(
|
||||
segment_id: int,
|
||||
async def async_remove_entity(
|
||||
index: int,
|
||||
coordinator: WLEDDataUpdateCoordinator,
|
||||
current: Dict[int, WLEDLight],
|
||||
current: Dict[int, WLEDSegmentLight],
|
||||
) -> None:
|
||||
"""Remove WLED segment light from Home Assistant."""
|
||||
entity = current[segment_id]
|
||||
entity = current[index]
|
||||
await entity.async_remove()
|
||||
registry = await async_get_entity_registry(coordinator.hass)
|
||||
if entity.entity_id in registry.entities:
|
||||
registry.async_remove(entity.entity_id)
|
||||
del current[segment_id]
|
||||
del current[index]
|
||||
|
@@ -3,7 +3,7 @@
|
||||
"name": "WLED",
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/wled",
|
||||
"requirements": ["wled==0.4.1"],
|
||||
"requirements": ["wled==0.4.2"],
|
||||
"zeroconf": ["_wled._tcp.local."],
|
||||
"codeowners": ["@frenck"],
|
||||
"quality_scale": "platinum"
|
||||
|
@@ -7,7 +7,7 @@
|
||||
"bellows==0.16.2",
|
||||
"pyserial==3.4",
|
||||
"zha-quirks==0.0.39",
|
||||
"zigpy-cc==0.4.2",
|
||||
"zigpy-cc==0.4.4",
|
||||
"zigpy-deconz==0.9.2",
|
||||
"zigpy==0.20.4",
|
||||
"zigpy-xbee==0.12.1",
|
||||
|
@@ -1,7 +1,7 @@
|
||||
"""Constants used by Home Assistant components."""
|
||||
MAJOR_VERSION = 0
|
||||
MINOR_VERSION = 111
|
||||
PATCH_VERSION = "0b2"
|
||||
PATCH_VERSION = "0b4"
|
||||
__short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
||||
__version__ = f"{__short_version__}.{PATCH_VERSION}"
|
||||
REQUIRED_PYTHON_VER = (3, 7, 0)
|
||||
|
@@ -12,10 +12,10 @@ cryptography==2.9.2
|
||||
defusedxml==0.6.0
|
||||
distro==1.5.0
|
||||
hass-nabucasa==0.34.5
|
||||
home-assistant-frontend==20200603.1
|
||||
home-assistant-frontend==20200603.2
|
||||
importlib-metadata==1.6.0
|
||||
jinja2>=2.11.1
|
||||
netdisco==2.6.0
|
||||
netdisco==2.7.0
|
||||
pip>=8.0.3
|
||||
python-slugify==4.0.0
|
||||
pytz>=2020.1
|
||||
|
@@ -734,7 +734,7 @@ hole==0.5.1
|
||||
holidays==0.10.2
|
||||
|
||||
# homeassistant.components.frontend
|
||||
home-assistant-frontend==20200603.1
|
||||
home-assistant-frontend==20200603.2
|
||||
|
||||
# homeassistant.components.zwave
|
||||
homeassistant-pyozw==0.1.10
|
||||
@@ -946,7 +946,7 @@ netdata==0.1.2
|
||||
|
||||
# homeassistant.components.discovery
|
||||
# homeassistant.components.ssdp
|
||||
netdisco==2.6.0
|
||||
netdisco==2.7.0
|
||||
|
||||
# homeassistant.components.neurio_energy
|
||||
neurio==0.3.1
|
||||
@@ -1245,7 +1245,7 @@ pycfdns==0.0.1
|
||||
pychannels==1.0.0
|
||||
|
||||
# homeassistant.components.cast
|
||||
pychromecast==5.3.0
|
||||
pychromecast==6.0.0
|
||||
|
||||
# homeassistant.components.cmus
|
||||
pycmus==0.1.1
|
||||
@@ -2195,7 +2195,7 @@ wirelesstagpy==0.4.0
|
||||
withings-api==2.1.3
|
||||
|
||||
# homeassistant.components.wled
|
||||
wled==0.4.1
|
||||
wled==0.4.2
|
||||
|
||||
# homeassistant.components.xbee
|
||||
xbee-helper==0.0.7
|
||||
@@ -2251,7 +2251,7 @@ zhong_hong_hvac==1.0.9
|
||||
ziggo-mediabox-xl==1.1.0
|
||||
|
||||
# homeassistant.components.zha
|
||||
zigpy-cc==0.4.2
|
||||
zigpy-cc==0.4.4
|
||||
|
||||
# homeassistant.components.zha
|
||||
zigpy-deconz==0.9.2
|
||||
|
@@ -321,7 +321,7 @@ hole==0.5.1
|
||||
holidays==0.10.2
|
||||
|
||||
# homeassistant.components.frontend
|
||||
home-assistant-frontend==20200603.1
|
||||
home-assistant-frontend==20200603.2
|
||||
|
||||
# homeassistant.components.zwave
|
||||
homeassistant-pyozw==0.1.10
|
||||
@@ -399,7 +399,7 @@ nessclient==0.9.15
|
||||
|
||||
# homeassistant.components.discovery
|
||||
# homeassistant.components.ssdp
|
||||
netdisco==2.6.0
|
||||
netdisco==2.7.0
|
||||
|
||||
# homeassistant.components.nexia
|
||||
nexia==0.9.3
|
||||
@@ -539,7 +539,7 @@ pyblackbird==0.5
|
||||
pybotvac==0.0.17
|
||||
|
||||
# homeassistant.components.cast
|
||||
pychromecast==5.3.0
|
||||
pychromecast==6.0.0
|
||||
|
||||
# homeassistant.components.coolmaster
|
||||
pycoolmasternet==0.0.4
|
||||
@@ -901,7 +901,7 @@ wiffi==1.0.0
|
||||
withings-api==2.1.3
|
||||
|
||||
# homeassistant.components.wled
|
||||
wled==0.4.1
|
||||
wled==0.4.2
|
||||
|
||||
# homeassistant.components.bluesound
|
||||
# homeassistant.components.rest
|
||||
@@ -921,7 +921,7 @@ zeroconf==0.27.1
|
||||
zha-quirks==0.0.39
|
||||
|
||||
# homeassistant.components.zha
|
||||
zigpy-cc==0.4.2
|
||||
zigpy-cc==0.4.4
|
||||
|
||||
# homeassistant.components.zha
|
||||
zigpy-deconz==0.9.2
|
||||
|
@@ -80,17 +80,17 @@ async def async_setup_cast_internal_discovery(hass, config=None, discovery_info=
|
||||
browser = MagicMock(zc={})
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.cast.discovery.pychromecast.discovery.CastListener",
|
||||
"homeassistant.components.cast.discovery.pychromecast.CastListener",
|
||||
return_value=listener,
|
||||
) as cast_listener, patch(
|
||||
"homeassistant.components.cast.discovery.zeroconf.ServiceBrowser",
|
||||
"homeassistant.components.cast.discovery.pychromecast.start_discovery",
|
||||
return_value=browser,
|
||||
):
|
||||
) as start_discovery:
|
||||
add_entities = await async_setup_cast(hass, config, discovery_info)
|
||||
await hass.async_block_till_done()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert cast_listener.call_count == 1
|
||||
assert start_discovery.call_count == 1
|
||||
|
||||
discovery_callback = cast_listener.call_args[0][0]
|
||||
|
||||
@@ -120,10 +120,10 @@ async def async_setup_media_player_cast(hass: HomeAssistantType, info: Chromecas
|
||||
"homeassistant.components.cast.discovery.pychromecast.get_chromecast_from_service",
|
||||
return_value=chromecast,
|
||||
) as get_chromecast, patch(
|
||||
"homeassistant.components.cast.discovery.pychromecast.discovery.CastListener",
|
||||
"homeassistant.components.cast.discovery.pychromecast.CastListener",
|
||||
return_value=listener,
|
||||
) as cast_listener, patch(
|
||||
"homeassistant.components.cast.discovery.zeroconf.ServiceBrowser",
|
||||
"homeassistant.components.cast.discovery.pychromecast.start_discovery",
|
||||
return_value=browser,
|
||||
):
|
||||
await async_setup_component(
|
||||
@@ -159,17 +159,15 @@ async def async_setup_media_player_cast(hass: HomeAssistantType, info: Chromecas
|
||||
async def test_start_discovery_called_once(hass):
|
||||
"""Test pychromecast.start_discovery called exactly once."""
|
||||
with patch(
|
||||
"homeassistant.components.cast.discovery.pychromecast.discovery.CastListener",
|
||||
) as cast_listener, patch(
|
||||
"homeassistant.components.cast.discovery.zeroconf.ServiceBrowser",
|
||||
"homeassistant.components.cast.discovery.pychromecast.start_discovery",
|
||||
return_value=Mock(),
|
||||
):
|
||||
) as start_discovery:
|
||||
await async_setup_cast(hass)
|
||||
|
||||
assert cast_listener.call_count == 1
|
||||
assert start_discovery.call_count == 1
|
||||
|
||||
await async_setup_cast(hass)
|
||||
assert cast_listener.call_count == 1
|
||||
assert start_discovery.call_count == 1
|
||||
|
||||
|
||||
async def test_stop_discovery_called_on_stop(hass):
|
||||
@@ -177,15 +175,13 @@ async def test_stop_discovery_called_on_stop(hass):
|
||||
browser = MagicMock(zc={})
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.cast.discovery.pychromecast.discovery.CastListener",
|
||||
) as cast_listener, patch(
|
||||
"homeassistant.components.cast.discovery.zeroconf.ServiceBrowser",
|
||||
"homeassistant.components.cast.discovery.pychromecast.start_discovery",
|
||||
return_value=browser,
|
||||
):
|
||||
) as start_discovery:
|
||||
# start_discovery should be called with empty config
|
||||
await async_setup_cast(hass, {})
|
||||
|
||||
assert cast_listener.call_count == 1
|
||||
assert start_discovery.call_count == 1
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.cast.discovery.pychromecast.stop_discovery"
|
||||
@@ -197,15 +193,13 @@ async def test_stop_discovery_called_on_stop(hass):
|
||||
stop_discovery.assert_called_once_with(browser)
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.cast.discovery.pychromecast.discovery.CastListener",
|
||||
) as cast_listener, patch(
|
||||
"homeassistant.components.cast.discovery.zeroconf.ServiceBrowser",
|
||||
"homeassistant.components.cast.discovery.pychromecast.start_discovery",
|
||||
return_value=browser,
|
||||
):
|
||||
) as start_discovery:
|
||||
# start_discovery should be called again on re-startup
|
||||
await async_setup_cast(hass)
|
||||
|
||||
assert cast_listener.call_count == 1
|
||||
assert start_discovery.call_count == 1
|
||||
|
||||
|
||||
async def test_create_cast_device_without_uuid(hass):
|
||||
|
@@ -1,12 +1,13 @@
|
||||
"""Fixtures for tests."""
|
||||
|
||||
from mock import patch
|
||||
import pytest
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from .common import ComponentFactory
|
||||
|
||||
from tests.async_mock import patch
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def component_factory(hass: HomeAssistant):
|
||||
|
@@ -4,8 +4,6 @@ import os
|
||||
import shutil
|
||||
from urllib.parse import urlencode
|
||||
|
||||
from mock import Mock, patch
|
||||
|
||||
from homeassistant.components.media_player.const import (
|
||||
ATTR_MEDIA_CONTENT_ID,
|
||||
DOMAIN as DOMAIN_MP,
|
||||
@@ -16,6 +14,7 @@ from homeassistant.config import async_process_ha_core_config
|
||||
from homeassistant.const import HTTP_INTERNAL_SERVER_ERROR
|
||||
from homeassistant.setup import setup_component
|
||||
|
||||
from tests.async_mock import Mock, patch
|
||||
from tests.common import assert_setup_component, get_test_home_assistant, mock_service
|
||||
|
||||
|
||||
|
@@ -2,7 +2,7 @@
|
||||
|
||||
import logging
|
||||
|
||||
from homeassistant.const import UNIT_PERCENTAGE
|
||||
from homeassistant.const import STATE_UNKNOWN, UNIT_PERCENTAGE
|
||||
from homeassistant.helpers import device_registry
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@@ -95,8 +95,8 @@ async def test_sensor_must_register(hass, create_registrations, webhook_client):
|
||||
assert json["battery_state"]["error"]["code"] == "not_registered"
|
||||
|
||||
|
||||
async def test_sensor_id_no_dupes(hass, create_registrations, webhook_client):
|
||||
"""Test that sensors must have a unique ID."""
|
||||
async def test_sensor_id_no_dupes(hass, create_registrations, webhook_client, caplog):
|
||||
"""Test that a duplicate unique ID in registration updates the sensor."""
|
||||
webhook_id = create_registrations[1]["webhook_id"]
|
||||
webhook_url = f"/api/webhook/{webhook_id}"
|
||||
|
||||
@@ -120,11 +120,115 @@ async def test_sensor_id_no_dupes(hass, create_registrations, webhook_client):
|
||||
|
||||
reg_json = await reg_resp.json()
|
||||
assert reg_json == {"success": True}
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert "Re-register existing sensor" not in caplog.text
|
||||
|
||||
entity = hass.states.get("sensor.test_1_battery_state")
|
||||
assert entity is not None
|
||||
|
||||
assert entity.attributes["device_class"] == "battery"
|
||||
assert entity.attributes["icon"] == "mdi:battery"
|
||||
assert entity.attributes["unit_of_measurement"] == UNIT_PERCENTAGE
|
||||
assert entity.attributes["foo"] == "bar"
|
||||
assert entity.domain == "sensor"
|
||||
assert entity.name == "Test 1 Battery State"
|
||||
assert entity.state == "100"
|
||||
|
||||
payload["data"]["state"] = 99
|
||||
dupe_resp = await webhook_client.post(webhook_url, json=payload)
|
||||
|
||||
assert dupe_resp.status == 409
|
||||
assert dupe_resp.status == 201
|
||||
dupe_reg_json = await dupe_resp.json()
|
||||
assert dupe_reg_json == {"success": True}
|
||||
await hass.async_block_till_done()
|
||||
|
||||
dupe_json = await dupe_resp.json()
|
||||
assert dupe_json["success"] is False
|
||||
assert dupe_json["error"]["code"] == "duplicate_unique_id"
|
||||
assert "Re-register existing sensor" in caplog.text
|
||||
|
||||
entity = hass.states.get("sensor.test_1_battery_state")
|
||||
assert entity is not None
|
||||
|
||||
assert entity.attributes["device_class"] == "battery"
|
||||
assert entity.attributes["icon"] == "mdi:battery"
|
||||
assert entity.attributes["unit_of_measurement"] == UNIT_PERCENTAGE
|
||||
assert entity.attributes["foo"] == "bar"
|
||||
assert entity.domain == "sensor"
|
||||
assert entity.name == "Test 1 Battery State"
|
||||
assert entity.state == "99"
|
||||
|
||||
|
||||
async def test_register_sensor_no_state(hass, create_registrations, webhook_client):
|
||||
"""Test that sensors can be registered, when there is no (unknown) state."""
|
||||
webhook_id = create_registrations[1]["webhook_id"]
|
||||
webhook_url = f"/api/webhook/{webhook_id}"
|
||||
|
||||
reg_resp = await webhook_client.post(
|
||||
webhook_url,
|
||||
json={
|
||||
"type": "register_sensor",
|
||||
"data": {
|
||||
"name": "Battery State",
|
||||
"state": None,
|
||||
"type": "sensor",
|
||||
"unique_id": "battery_state",
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
assert reg_resp.status == 201
|
||||
|
||||
json = await reg_resp.json()
|
||||
assert json == {"success": True}
|
||||
await hass.async_block_till_done()
|
||||
|
||||
entity = hass.states.get("sensor.test_1_battery_state")
|
||||
assert entity is not None
|
||||
|
||||
assert entity.domain == "sensor"
|
||||
assert entity.name == "Test 1 Battery State"
|
||||
assert entity.state == STATE_UNKNOWN
|
||||
|
||||
|
||||
async def test_update_sensor_no_state(hass, create_registrations, webhook_client):
|
||||
"""Test that sensors can be updated, when there is no (unknown) state."""
|
||||
webhook_id = create_registrations[1]["webhook_id"]
|
||||
webhook_url = f"/api/webhook/{webhook_id}"
|
||||
|
||||
reg_resp = await webhook_client.post(
|
||||
webhook_url,
|
||||
json={
|
||||
"type": "register_sensor",
|
||||
"data": {
|
||||
"name": "Battery State",
|
||||
"state": 100,
|
||||
"type": "sensor",
|
||||
"unique_id": "battery_state",
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
assert reg_resp.status == 201
|
||||
|
||||
json = await reg_resp.json()
|
||||
assert json == {"success": True}
|
||||
await hass.async_block_till_done()
|
||||
|
||||
entity = hass.states.get("sensor.test_1_battery_state")
|
||||
assert entity is not None
|
||||
assert entity.state == "100"
|
||||
|
||||
update_resp = await webhook_client.post(
|
||||
webhook_url,
|
||||
json={
|
||||
"type": "update_sensor_states",
|
||||
"data": [{"state": None, "type": "sensor", "unique_id": "battery_state"}],
|
||||
},
|
||||
)
|
||||
|
||||
assert update_resp.status == 200
|
||||
|
||||
json = await update_resp.json()
|
||||
assert json == {"battery_state": {"success": True}}
|
||||
|
||||
updated_entity = hass.states.get("sensor.test_1_battery_state")
|
||||
assert updated_entity.state == STATE_UNKNOWN
|
||||
|
@@ -2,7 +2,6 @@
|
||||
import datetime
|
||||
from typing import Union
|
||||
|
||||
import mock
|
||||
from py17track.package import Package
|
||||
import pytest
|
||||
|
||||
@@ -14,6 +13,7 @@ from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
||||
from homeassistant.setup import async_setup_component
|
||||
from homeassistant.util import utcnow
|
||||
|
||||
from tests.async_mock import MagicMock, patch
|
||||
from tests.common import async_fire_time_changed
|
||||
|
||||
VALID_CONFIG_MINIMAL = {
|
||||
@@ -113,7 +113,7 @@ class ProfileMock:
|
||||
@pytest.fixture(autouse=True, name="mock_client")
|
||||
def fixture_mock_client():
|
||||
"""Mock py17track client."""
|
||||
with mock.patch(
|
||||
with patch(
|
||||
"homeassistant.components.seventeentrack.sensor.SeventeenTrackClient",
|
||||
new=ClientMock,
|
||||
):
|
||||
@@ -137,7 +137,7 @@ async def _goto_future(hass, future=None):
|
||||
"""Move to future."""
|
||||
if not future:
|
||||
future = utcnow() + datetime.timedelta(minutes=10)
|
||||
with mock.patch("homeassistant.util.utcnow", return_value=future):
|
||||
with patch("homeassistant.util.utcnow", return_value=future):
|
||||
async_fire_time_changed(hass, future)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
@@ -245,7 +245,7 @@ async def test_delivered_not_shown(hass):
|
||||
)
|
||||
ProfileMock.package_list = [package]
|
||||
|
||||
hass.components.persistent_notification = mock.MagicMock()
|
||||
hass.components.persistent_notification = MagicMock()
|
||||
await _setup_seventeentrack(hass, VALID_CONFIG_FULL_NO_DELIVERED)
|
||||
assert not hass.states.async_entity_ids()
|
||||
hass.components.persistent_notification.create.assert_called()
|
||||
@@ -258,7 +258,7 @@ async def test_delivered_shown(hass):
|
||||
)
|
||||
ProfileMock.package_list = [package]
|
||||
|
||||
hass.components.persistent_notification = mock.MagicMock()
|
||||
hass.components.persistent_notification = MagicMock()
|
||||
await _setup_seventeentrack(hass, VALID_CONFIG_FULL)
|
||||
|
||||
assert hass.states.get("sensor.seventeentrack_package_456") is not None
|
||||
@@ -283,7 +283,7 @@ async def test_becomes_delivered_not_shown_notification(hass):
|
||||
)
|
||||
ProfileMock.package_list = [package_delivered]
|
||||
|
||||
hass.components.persistent_notification = mock.MagicMock()
|
||||
hass.components.persistent_notification = MagicMock()
|
||||
await _goto_future(hass)
|
||||
|
||||
hass.components.persistent_notification.create.assert_called()
|
||||
|
@@ -2,13 +2,13 @@
|
||||
|
||||
from typing import Callable, Dict, NamedTuple, Tuple
|
||||
|
||||
from mock import MagicMock
|
||||
import pyvera as pv
|
||||
|
||||
from homeassistant.components.vera.const import CONF_CONTROLLER, DOMAIN
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from tests.async_mock import MagicMock
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
SetupCallback = Callable[[pv.VeraController, dict], None]
|
||||
|
@@ -1,10 +1,11 @@
|
||||
"""Fixtures for tests."""
|
||||
|
||||
from mock import patch
|
||||
import pytest
|
||||
|
||||
from .common import ComponentFactory
|
||||
|
||||
from tests.async_mock import patch
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def vera_component_factory():
|
||||
|
@@ -1,5 +1,4 @@
|
||||
"""Vera tests."""
|
||||
from mock import patch
|
||||
from requests.exceptions import RequestException
|
||||
|
||||
from homeassistant import config_entries, data_entry_flow
|
||||
@@ -12,7 +11,7 @@ from homeassistant.data_entry_flow import (
|
||||
RESULT_TYPE_FORM,
|
||||
)
|
||||
|
||||
from tests.async_mock import MagicMock
|
||||
from tests.async_mock import MagicMock, patch
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
|
@@ -29,6 +29,7 @@ from homeassistant.const import (
|
||||
ATTR_ICON,
|
||||
SERVICE_TURN_OFF,
|
||||
SERVICE_TURN_ON,
|
||||
STATE_OFF,
|
||||
STATE_ON,
|
||||
STATE_UNAVAILABLE,
|
||||
)
|
||||
@@ -50,7 +51,7 @@ async def test_rgb_light_state(
|
||||
entity_registry = await hass.helpers.entity_registry.async_get_registry()
|
||||
|
||||
# First segment of the strip
|
||||
state = hass.states.get("light.wled_rgb_light")
|
||||
state = hass.states.get("light.wled_rgb_light_segment_0")
|
||||
assert state
|
||||
assert state.attributes.get(ATTR_BRIGHTNESS) == 127
|
||||
assert state.attributes.get(ATTR_EFFECT) == "Solid"
|
||||
@@ -64,12 +65,12 @@ async def test_rgb_light_state(
|
||||
assert state.attributes.get(ATTR_SPEED) == 32
|
||||
assert state.state == STATE_ON
|
||||
|
||||
entry = entity_registry.async_get("light.wled_rgb_light")
|
||||
entry = entity_registry.async_get("light.wled_rgb_light_segment_0")
|
||||
assert entry
|
||||
assert entry.unique_id == "aabbccddeeff_0"
|
||||
|
||||
# Second segment of the strip
|
||||
state = hass.states.get("light.wled_rgb_light_1")
|
||||
state = hass.states.get("light.wled_rgb_light_segment_1")
|
||||
assert state
|
||||
assert state.attributes.get(ATTR_BRIGHTNESS) == 127
|
||||
assert state.attributes.get(ATTR_EFFECT) == "Blink"
|
||||
@@ -83,22 +84,32 @@ async def test_rgb_light_state(
|
||||
assert state.attributes.get(ATTR_SPEED) == 16
|
||||
assert state.state == STATE_ON
|
||||
|
||||
entry = entity_registry.async_get("light.wled_rgb_light_1")
|
||||
entry = entity_registry.async_get("light.wled_rgb_light_segment_1")
|
||||
assert entry
|
||||
assert entry.unique_id == "aabbccddeeff_1"
|
||||
|
||||
# Test master control of the lightstrip
|
||||
state = hass.states.get("light.wled_rgb_light_master")
|
||||
assert state
|
||||
assert state.attributes.get(ATTR_BRIGHTNESS) == 127
|
||||
assert state.state == STATE_ON
|
||||
|
||||
async def test_switch_change_state(
|
||||
entry = entity_registry.async_get("light.wled_rgb_light_master")
|
||||
assert entry
|
||||
assert entry.unique_id == "aabbccddeeff"
|
||||
|
||||
|
||||
async def test_segment_change_state(
|
||||
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker, caplog
|
||||
) -> None:
|
||||
"""Test the change of state of the WLED switches."""
|
||||
"""Test the change of state of the WLED segments."""
|
||||
await init_integration(hass, aioclient_mock)
|
||||
|
||||
with patch("wled.WLED.light") as light_mock:
|
||||
with patch("wled.WLED.segment") as light_mock:
|
||||
await hass.services.async_call(
|
||||
LIGHT_DOMAIN,
|
||||
SERVICE_TURN_OFF,
|
||||
{ATTR_ENTITY_ID: "light.wled_rgb_light", ATTR_TRANSITION: 5},
|
||||
{ATTR_ENTITY_ID: "light.wled_rgb_light_segment_0", ATTR_TRANSITION: 5},
|
||||
blocking=True,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
@@ -106,14 +117,14 @@ async def test_switch_change_state(
|
||||
on=False, segment_id=0, transition=50,
|
||||
)
|
||||
|
||||
with patch("wled.WLED.light") as light_mock:
|
||||
with patch("wled.WLED.segment") as light_mock:
|
||||
await hass.services.async_call(
|
||||
LIGHT_DOMAIN,
|
||||
SERVICE_TURN_ON,
|
||||
{
|
||||
ATTR_BRIGHTNESS: 42,
|
||||
ATTR_EFFECT: "Chase",
|
||||
ATTR_ENTITY_ID: "light.wled_rgb_light",
|
||||
ATTR_ENTITY_ID: "light.wled_rgb_light_segment_0",
|
||||
ATTR_RGB_COLOR: [255, 0, 0],
|
||||
ATTR_TRANSITION: 5,
|
||||
},
|
||||
@@ -129,11 +140,11 @@ async def test_switch_change_state(
|
||||
transition=50,
|
||||
)
|
||||
|
||||
with patch("wled.WLED.light") as light_mock:
|
||||
with patch("wled.WLED.segment") as light_mock:
|
||||
await hass.services.async_call(
|
||||
LIGHT_DOMAIN,
|
||||
SERVICE_TURN_ON,
|
||||
{ATTR_ENTITY_ID: "light.wled_rgb_light", ATTR_COLOR_TEMP: 400},
|
||||
{ATTR_ENTITY_ID: "light.wled_rgb_light_segment_0", ATTR_COLOR_TEMP: 400},
|
||||
blocking=True,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
@@ -142,33 +153,178 @@ async def test_switch_change_state(
|
||||
)
|
||||
|
||||
|
||||
async def test_master_change_state(
|
||||
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker, caplog
|
||||
) -> None:
|
||||
"""Test the change of state of the WLED master light control."""
|
||||
await init_integration(hass, aioclient_mock)
|
||||
|
||||
with patch("wled.WLED.master") as light_mock:
|
||||
await hass.services.async_call(
|
||||
LIGHT_DOMAIN,
|
||||
SERVICE_TURN_OFF,
|
||||
{ATTR_ENTITY_ID: "light.wled_rgb_light_master", ATTR_TRANSITION: 5},
|
||||
blocking=True,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
light_mock.assert_called_once_with(
|
||||
on=False, transition=50,
|
||||
)
|
||||
|
||||
with patch("wled.WLED.master") as light_mock:
|
||||
await hass.services.async_call(
|
||||
LIGHT_DOMAIN,
|
||||
SERVICE_TURN_ON,
|
||||
{
|
||||
ATTR_BRIGHTNESS: 42,
|
||||
ATTR_ENTITY_ID: "light.wled_rgb_light_master",
|
||||
ATTR_TRANSITION: 5,
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
light_mock.assert_called_once_with(
|
||||
brightness=42, on=True, transition=50,
|
||||
)
|
||||
|
||||
with patch("wled.WLED.master") as light_mock:
|
||||
await hass.services.async_call(
|
||||
LIGHT_DOMAIN,
|
||||
SERVICE_TURN_OFF,
|
||||
{ATTR_ENTITY_ID: "light.wled_rgb_light_master", ATTR_TRANSITION: 5},
|
||||
blocking=True,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
light_mock.assert_called_once_with(
|
||||
on=False, transition=50,
|
||||
)
|
||||
|
||||
with patch("wled.WLED.master") as light_mock:
|
||||
await hass.services.async_call(
|
||||
LIGHT_DOMAIN,
|
||||
SERVICE_TURN_ON,
|
||||
{
|
||||
ATTR_BRIGHTNESS: 42,
|
||||
ATTR_ENTITY_ID: "light.wled_rgb_light_master",
|
||||
ATTR_TRANSITION: 5,
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
light_mock.assert_called_once_with(
|
||||
brightness=42, on=True, transition=50,
|
||||
)
|
||||
|
||||
|
||||
async def test_dynamically_handle_segments(
|
||||
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker
|
||||
) -> None:
|
||||
"""Test if a new/deleted segment is dynamically added/removed."""
|
||||
await init_integration(hass, aioclient_mock)
|
||||
|
||||
assert hass.states.get("light.wled_rgb_light")
|
||||
assert hass.states.get("light.wled_rgb_light_1")
|
||||
assert hass.states.get("light.wled_rgb_light_master")
|
||||
assert hass.states.get("light.wled_rgb_light_segment_0")
|
||||
assert hass.states.get("light.wled_rgb_light_segment_1")
|
||||
|
||||
data = json.loads(load_fixture("wled/rgb_single_segment.json"))
|
||||
device = WLEDDevice(data)
|
||||
|
||||
# Test removal if segment went missing
|
||||
# Test removal if segment went missing, including the master entity
|
||||
with patch(
|
||||
"homeassistant.components.wled.WLED.update", return_value=device,
|
||||
):
|
||||
async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL)
|
||||
await hass.async_block_till_done()
|
||||
assert hass.states.get("light.wled_rgb_light")
|
||||
assert not hass.states.get("light.wled_rgb_light_1")
|
||||
assert hass.states.get("light.wled_rgb_light_segment_0")
|
||||
assert not hass.states.get("light.wled_rgb_light_segment_1")
|
||||
assert not hass.states.get("light.wled_rgb_light_master")
|
||||
|
||||
# Test adding if segment shows up again
|
||||
# Test adding if segment shows up again, including the master entity
|
||||
async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert hass.states.get("light.wled_rgb_light")
|
||||
assert hass.states.get("light.wled_rgb_light_1")
|
||||
assert hass.states.get("light.wled_rgb_light_master")
|
||||
assert hass.states.get("light.wled_rgb_light_segment_0")
|
||||
assert hass.states.get("light.wled_rgb_light_segment_1")
|
||||
|
||||
|
||||
async def test_single_segment_behavior(
|
||||
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker, caplog
|
||||
) -> None:
|
||||
"""Test the behavior of the integration with a single segment."""
|
||||
await init_integration(hass, aioclient_mock)
|
||||
|
||||
data = json.loads(load_fixture("wled/rgb_single_segment.json"))
|
||||
device = WLEDDevice(data)
|
||||
|
||||
# Test absent master
|
||||
with patch(
|
||||
"homeassistant.components.wled.WLED.update", return_value=device,
|
||||
):
|
||||
async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert not hass.states.get("light.wled_rgb_light_master")
|
||||
|
||||
state = hass.states.get("light.wled_rgb_light_segment_0")
|
||||
assert state
|
||||
assert state.state == STATE_ON
|
||||
|
||||
# Test segment brightness takes master into account
|
||||
device.state.brightness = 100
|
||||
device.state.segments[0].brightness = 255
|
||||
with patch(
|
||||
"homeassistant.components.wled.WLED.update", return_value=device,
|
||||
):
|
||||
async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("light.wled_rgb_light_segment_0")
|
||||
assert state
|
||||
assert state.attributes.get(ATTR_BRIGHTNESS) == 100
|
||||
|
||||
# Test segment is off when master is off
|
||||
device.state.on = False
|
||||
with patch(
|
||||
"homeassistant.components.wled.WLED.update", return_value=device,
|
||||
):
|
||||
async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL)
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get("light.wled_rgb_light_segment_0")
|
||||
assert state
|
||||
assert state.state == STATE_OFF
|
||||
|
||||
# Test master is turned off when turning off a single segment
|
||||
with patch("wled.WLED.master") as master_mock:
|
||||
await hass.services.async_call(
|
||||
LIGHT_DOMAIN,
|
||||
SERVICE_TURN_OFF,
|
||||
{ATTR_ENTITY_ID: "light.wled_rgb_light_segment_0", ATTR_TRANSITION: 5},
|
||||
blocking=True,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
master_mock.assert_called_once_with(
|
||||
on=False, transition=50,
|
||||
)
|
||||
|
||||
# Test master is turned on when turning on a single segment, and segment
|
||||
# brightness is set to 255.
|
||||
with patch("wled.WLED.master") as master_mock, patch(
|
||||
"wled.WLED.segment"
|
||||
) as segment_mock:
|
||||
await hass.services.async_call(
|
||||
LIGHT_DOMAIN,
|
||||
SERVICE_TURN_ON,
|
||||
{
|
||||
ATTR_ENTITY_ID: "light.wled_rgb_light_segment_0",
|
||||
ATTR_TRANSITION: 5,
|
||||
ATTR_BRIGHTNESS: 42,
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
master_mock.assert_called_once_with(on=True, transition=50, brightness=42)
|
||||
segment_mock.assert_called_once_with(on=True, segment_id=0, brightness=255)
|
||||
|
||||
|
||||
async def test_light_error(
|
||||
@@ -182,12 +338,12 @@ async def test_light_error(
|
||||
await hass.services.async_call(
|
||||
LIGHT_DOMAIN,
|
||||
SERVICE_TURN_OFF,
|
||||
{ATTR_ENTITY_ID: "light.wled_rgb_light"},
|
||||
{ATTR_ENTITY_ID: "light.wled_rgb_light_segment_0"},
|
||||
blocking=True,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("light.wled_rgb_light")
|
||||
state = hass.states.get("light.wled_rgb_light_segment_0")
|
||||
assert state.state == STATE_ON
|
||||
assert "Invalid response from API" in caplog.text
|
||||
|
||||
@@ -199,17 +355,17 @@ async def test_light_connection_error(
|
||||
await init_integration(hass, aioclient_mock)
|
||||
|
||||
with patch("homeassistant.components.wled.WLED.update"), patch(
|
||||
"homeassistant.components.wled.WLED.light", side_effect=WLEDConnectionError
|
||||
"homeassistant.components.wled.WLED.segment", side_effect=WLEDConnectionError
|
||||
):
|
||||
await hass.services.async_call(
|
||||
LIGHT_DOMAIN,
|
||||
SERVICE_TURN_OFF,
|
||||
{ATTR_ENTITY_ID: "light.wled_rgb_light"},
|
||||
{ATTR_ENTITY_ID: "light.wled_rgb_light_segment_0"},
|
||||
blocking=True,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("light.wled_rgb_light")
|
||||
state = hass.states.get("light.wled_rgb_light_segment_0")
|
||||
assert state.state == STATE_UNAVAILABLE
|
||||
|
||||
|
||||
@@ -224,7 +380,7 @@ async def test_rgbw_light(
|
||||
assert state.attributes.get(ATTR_HS_COLOR) == (0.0, 100.0)
|
||||
assert state.attributes.get(ATTR_WHITE_VALUE) == 139
|
||||
|
||||
with patch("wled.WLED.light") as light_mock:
|
||||
with patch("wled.WLED.segment") as light_mock:
|
||||
await hass.services.async_call(
|
||||
LIGHT_DOMAIN,
|
||||
SERVICE_TURN_ON,
|
||||
@@ -236,7 +392,7 @@ async def test_rgbw_light(
|
||||
on=True, segment_id=0, color_primary=(255, 159, 70, 139),
|
||||
)
|
||||
|
||||
with patch("wled.WLED.light") as light_mock:
|
||||
with patch("wled.WLED.segment") as light_mock:
|
||||
await hass.services.async_call(
|
||||
LIGHT_DOMAIN,
|
||||
SERVICE_TURN_ON,
|
||||
@@ -248,7 +404,7 @@ async def test_rgbw_light(
|
||||
color_primary=(255, 0, 0, 100), on=True, segment_id=0,
|
||||
)
|
||||
|
||||
with patch("wled.WLED.light") as light_mock:
|
||||
with patch("wled.WLED.segment") as light_mock:
|
||||
await hass.services.async_call(
|
||||
LIGHT_DOMAIN,
|
||||
SERVICE_TURN_ON,
|
||||
@@ -271,13 +427,13 @@ async def test_effect_service(
|
||||
"""Test the effect service of a WLED light."""
|
||||
await init_integration(hass, aioclient_mock)
|
||||
|
||||
with patch("wled.WLED.light") as light_mock:
|
||||
with patch("wled.WLED.segment") as light_mock:
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_EFFECT,
|
||||
{
|
||||
ATTR_EFFECT: "Rainbow",
|
||||
ATTR_ENTITY_ID: "light.wled_rgb_light",
|
||||
ATTR_ENTITY_ID: "light.wled_rgb_light_segment_0",
|
||||
ATTR_INTENSITY: 200,
|
||||
ATTR_REVERSE: True,
|
||||
ATTR_SPEED: 100,
|
||||
@@ -289,11 +445,11 @@ async def test_effect_service(
|
||||
effect="Rainbow", intensity=200, reverse=True, segment_id=0, speed=100,
|
||||
)
|
||||
|
||||
with patch("wled.WLED.light") as light_mock:
|
||||
with patch("wled.WLED.segment") as light_mock:
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_EFFECT,
|
||||
{ATTR_ENTITY_ID: "light.wled_rgb_light", ATTR_EFFECT: 9},
|
||||
{ATTR_ENTITY_ID: "light.wled_rgb_light_segment_0", ATTR_EFFECT: 9},
|
||||
blocking=True,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
@@ -301,12 +457,12 @@ async def test_effect_service(
|
||||
segment_id=0, effect=9,
|
||||
)
|
||||
|
||||
with patch("wled.WLED.light") as light_mock:
|
||||
with patch("wled.WLED.segment") as light_mock:
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_EFFECT,
|
||||
{
|
||||
ATTR_ENTITY_ID: "light.wled_rgb_light",
|
||||
ATTR_ENTITY_ID: "light.wled_rgb_light_segment_0",
|
||||
ATTR_INTENSITY: 200,
|
||||
ATTR_REVERSE: True,
|
||||
ATTR_SPEED: 100,
|
||||
@@ -318,13 +474,13 @@ async def test_effect_service(
|
||||
intensity=200, reverse=True, segment_id=0, speed=100,
|
||||
)
|
||||
|
||||
with patch("wled.WLED.light") as light_mock:
|
||||
with patch("wled.WLED.segment") as light_mock:
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_EFFECT,
|
||||
{
|
||||
ATTR_EFFECT: "Rainbow",
|
||||
ATTR_ENTITY_ID: "light.wled_rgb_light",
|
||||
ATTR_ENTITY_ID: "light.wled_rgb_light_segment_0",
|
||||
ATTR_REVERSE: True,
|
||||
ATTR_SPEED: 100,
|
||||
},
|
||||
@@ -335,13 +491,13 @@ async def test_effect_service(
|
||||
effect="Rainbow", reverse=True, segment_id=0, speed=100,
|
||||
)
|
||||
|
||||
with patch("wled.WLED.light") as light_mock:
|
||||
with patch("wled.WLED.segment") as light_mock:
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_EFFECT,
|
||||
{
|
||||
ATTR_EFFECT: "Rainbow",
|
||||
ATTR_ENTITY_ID: "light.wled_rgb_light",
|
||||
ATTR_ENTITY_ID: "light.wled_rgb_light_segment_0",
|
||||
ATTR_INTENSITY: 200,
|
||||
ATTR_SPEED: 100,
|
||||
},
|
||||
@@ -352,13 +508,13 @@ async def test_effect_service(
|
||||
effect="Rainbow", intensity=200, segment_id=0, speed=100,
|
||||
)
|
||||
|
||||
with patch("wled.WLED.light") as light_mock:
|
||||
with patch("wled.WLED.segment") as light_mock:
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_EFFECT,
|
||||
{
|
||||
ATTR_EFFECT: "Rainbow",
|
||||
ATTR_ENTITY_ID: "light.wled_rgb_light",
|
||||
ATTR_ENTITY_ID: "light.wled_rgb_light_segment_0",
|
||||
ATTR_INTENSITY: 200,
|
||||
ATTR_REVERSE: True,
|
||||
},
|
||||
@@ -381,11 +537,11 @@ async def test_effect_service_error(
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_EFFECT,
|
||||
{ATTR_ENTITY_ID: "light.wled_rgb_light", ATTR_EFFECT: 9},
|
||||
{ATTR_ENTITY_ID: "light.wled_rgb_light_segment_0", ATTR_EFFECT: 9},
|
||||
blocking=True,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("light.wled_rgb_light")
|
||||
state = hass.states.get("light.wled_rgb_light_segment_0")
|
||||
assert state.state == STATE_ON
|
||||
assert "Invalid response from API" in caplog.text
|
||||
|
Reference in New Issue
Block a user