mirror of
https://github.com/home-assistant/core.git
synced 2025-10-09 11:49:27 +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)
|
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):
|
class UnknownRequest(HomeAssistantError):
|
||||||
"""When an unknown Alexa request is passed in."""
|
"""When an unknown Alexa request is passed in."""
|
||||||
|
|
||||||
|
@@ -3,7 +3,6 @@ import logging
|
|||||||
import threading
|
import threading
|
||||||
|
|
||||||
import pychromecast
|
import pychromecast
|
||||||
import zeroconf
|
|
||||||
|
|
||||||
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
|
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
@@ -85,14 +84,12 @@ def setup_internal_discovery(hass: HomeAssistant) -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
_LOGGER.debug("Starting internal pychromecast discovery.")
|
_LOGGER.debug("Starting internal pychromecast discovery.")
|
||||||
listener = pychromecast.discovery.CastListener(
|
listener = pychromecast.CastListener(
|
||||||
internal_add_callback, internal_remove_callback
|
internal_add_callback,
|
||||||
)
|
internal_remove_callback,
|
||||||
browser = zeroconf.ServiceBrowser(
|
internal_add_callback, # Use internal_add_callback also for updates
|
||||||
ChromeCastZeroconf.get_zeroconf() or zeroconf.Zeroconf(),
|
|
||||||
"_googlecast._tcp.local.",
|
|
||||||
listener,
|
|
||||||
)
|
)
|
||||||
|
browser = pychromecast.start_discovery(listener, ChromeCastZeroconf.get_zeroconf())
|
||||||
|
|
||||||
def stop_discovery(event):
|
def stop_discovery(event):
|
||||||
"""Stop discovery of new chromecasts."""
|
"""Stop discovery of new chromecasts."""
|
||||||
|
@@ -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==5.3.0"],
|
"requirements": ["pychromecast==6.0.0"],
|
||||||
"after_dependencies": ["cloud","zeroconf"],
|
"after_dependencies": ["cloud","zeroconf"],
|
||||||
"zeroconf": ["_googlecast._tcp.local."],
|
"zeroconf": ["_googlecast._tcp.local."],
|
||||||
"codeowners": ["@emontnemery"]
|
"codeowners": ["@emontnemery"]
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
"domain": "discovery",
|
"domain": "discovery",
|
||||||
"name": "Discovery",
|
"name": "Discovery",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/discovery",
|
"documentation": "https://www.home-assistant.io/integrations/discovery",
|
||||||
"requirements": ["netdisco==2.6.0"],
|
"requirements": ["netdisco==2.7.0"],
|
||||||
"after_dependencies": ["zeroconf"],
|
"after_dependencies": ["zeroconf"],
|
||||||
"codeowners": [],
|
"codeowners": [],
|
||||||
"quality_scale": "internal"
|
"quality_scale": "internal"
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
"domain": "frontend",
|
"domain": "frontend",
|
||||||
"name": "Home Assistant Frontend",
|
"name": "Home Assistant Frontend",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/frontend",
|
"documentation": "https://www.home-assistant.io/integrations/frontend",
|
||||||
"requirements": ["home-assistant-frontend==20200603.1"],
|
"requirements": ["home-assistant-frontend==20200603.2"],
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
"api",
|
"api",
|
||||||
"auth",
|
"auth",
|
||||||
|
@@ -232,7 +232,7 @@ NODE_FILTERS = {
|
|||||||
"RemoteLinc2_ADV",
|
"RemoteLinc2_ADV",
|
||||||
],
|
],
|
||||||
FILTER_INSTEON_TYPE: ["0.16.", "0.17.", "0.18.", "9.0.", "9.7."],
|
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: {
|
LOCK: {
|
||||||
FILTER_UOM: ["11"],
|
FILTER_UOM: ["11"],
|
||||||
|
@@ -56,7 +56,6 @@ ERR_ENCRYPTION_ALREADY_ENABLED = "encryption_already_enabled"
|
|||||||
ERR_ENCRYPTION_NOT_AVAILABLE = "encryption_not_available"
|
ERR_ENCRYPTION_NOT_AVAILABLE = "encryption_not_available"
|
||||||
ERR_ENCRYPTION_REQUIRED = "encryption_required"
|
ERR_ENCRYPTION_REQUIRED = "encryption_required"
|
||||||
ERR_SENSOR_NOT_REGISTERED = "not_registered"
|
ERR_SENSOR_NOT_REGISTERED = "not_registered"
|
||||||
ERR_SENSOR_DUPLICATE_UNIQUE_ID = "duplicate_unique_id"
|
|
||||||
ERR_INVALID_FORMAT = "invalid_format"
|
ERR_INVALID_FORMAT = "invalid_format"
|
||||||
|
|
||||||
|
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
"""Webhook handlers for mobile_app."""
|
"""Webhook handlers for mobile_app."""
|
||||||
|
import asyncio
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
import logging
|
import logging
|
||||||
import secrets
|
import secrets
|
||||||
@@ -28,7 +29,7 @@ from homeassistant.const import (
|
|||||||
HTTP_CREATED,
|
HTTP_CREATED,
|
||||||
)
|
)
|
||||||
from homeassistant.core import EventOrigin
|
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 import config_validation as cv, device_registry as dr
|
||||||
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||||
from homeassistant.helpers.template import attach
|
from homeassistant.helpers.template import attach
|
||||||
@@ -77,7 +78,6 @@ from .const import (
|
|||||||
ERR_ENCRYPTION_NOT_AVAILABLE,
|
ERR_ENCRYPTION_NOT_AVAILABLE,
|
||||||
ERR_ENCRYPTION_REQUIRED,
|
ERR_ENCRYPTION_REQUIRED,
|
||||||
ERR_INVALID_FORMAT,
|
ERR_INVALID_FORMAT,
|
||||||
ERR_SENSOR_DUPLICATE_UNIQUE_ID,
|
|
||||||
ERR_SENSOR_NOT_REGISTERED,
|
ERR_SENSOR_NOT_REGISTERED,
|
||||||
SIGNAL_LOCATION_UPDATE,
|
SIGNAL_LOCATION_UPDATE,
|
||||||
SIGNAL_SENSOR_UPDATE,
|
SIGNAL_SENSOR_UPDATE,
|
||||||
@@ -95,6 +95,7 @@ from .helpers import (
|
|||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
DELAY_SAVE = 10
|
||||||
|
|
||||||
WEBHOOK_COMMANDS = Registry()
|
WEBHOOK_COMMANDS = Registry()
|
||||||
|
|
||||||
@@ -184,7 +185,10 @@ async def handle_webhook(
|
|||||||
"Received webhook payload for type %s: %s", webhook_type, webhook_payload
|
"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")
|
@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_TYPE): vol.In(SENSOR_TYPES),
|
||||||
vol.Required(ATTR_SENSOR_UNIQUE_ID): cv.string,
|
vol.Required(ATTR_SENSOR_UNIQUE_ID): cv.string,
|
||||||
vol.Optional(ATTR_SENSOR_UOM): 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,
|
vol.Optional(ATTR_SENSOR_ICON, default="mdi:cellphone"): cv.icon,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
async def webhook_register_sensor(hass, config_entry, data):
|
async def webhook_register_sensor(hass, config_entry, data):
|
||||||
"""Handle a register sensor webhook."""
|
"""Handle a register sensor webhook."""
|
||||||
entity_type = data[ATTR_SENSOR_TYPE]
|
entity_type = data[ATTR_SENSOR_TYPE]
|
||||||
|
|
||||||
unique_id = data[ATTR_SENSOR_UNIQUE_ID]
|
unique_id = data[ATTR_SENSOR_UNIQUE_ID]
|
||||||
|
|
||||||
unique_store_key = f"{config_entry.data[CONF_WEBHOOK_ID]}_{unique_id}"
|
unique_store_key = f"{config_entry.data[CONF_WEBHOOK_ID]}_{unique_id}"
|
||||||
|
existing_sensor = unique_store_key in hass.data[DOMAIN][entity_type]
|
||||||
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,
|
|
||||||
)
|
|
||||||
|
|
||||||
data[CONF_WEBHOOK_ID] = config_entry.data[CONF_WEBHOOK_ID]
|
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
|
hass.data[DOMAIN][entity_type][unique_store_key] = data
|
||||||
|
|
||||||
try:
|
hass.data[DOMAIN][DATA_STORE].async_delay_save(
|
||||||
await hass.data[DOMAIN][DATA_STORE].async_save(savable_state(hass))
|
lambda: savable_state(hass), DELAY_SAVE
|
||||||
except HomeAssistantError as ex:
|
)
|
||||||
_LOGGER.error("Error registering sensor: %s", ex)
|
|
||||||
return empty_okay_response()
|
|
||||||
|
|
||||||
register_signal = f"{DOMAIN}_{data[ATTR_SENSOR_TYPE]}_register"
|
if existing_sensor:
|
||||||
async_dispatcher_send(hass, register_signal, data)
|
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(
|
return webhook_response(
|
||||||
{"success": True}, registration=config_entry.data, status=HTTP_CREATED,
|
{"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_ATTRIBUTES, default={}): dict,
|
||||||
vol.Optional(ATTR_SENSOR_ICON, default="mdi:cellphone"): cv.icon,
|
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_TYPE): vol.In(SENSOR_TYPES),
|
||||||
vol.Required(ATTR_SENSOR_UNIQUE_ID): cv.string,
|
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
|
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)
|
async_dispatcher_send(hass, SIGNAL_SENSOR_UPDATE, new_state)
|
||||||
|
|
||||||
resp[unique_id] = {"success": True}
|
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)
|
return webhook_response(resp, registration=config_entry.data)
|
||||||
|
|
||||||
|
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
"domain": "ssdp",
|
"domain": "ssdp",
|
||||||
"name": "Simple Service Discovery Protocol (SSDP)",
|
"name": "Simple Service Discovery Protocol (SSDP)",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/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"],
|
"after_dependencies": ["zeroconf"],
|
||||||
"codeowners": []
|
"codeowners": []
|
||||||
}
|
}
|
||||||
|
@@ -83,20 +83,78 @@ async def async_setup_entry(
|
|||||||
update_segments()
|
update_segments()
|
||||||
|
|
||||||
|
|
||||||
class WLEDLight(LightEntity, WLEDDeviceEntity):
|
class WLEDMasterLight(LightEntity, WLEDDeviceEntity):
|
||||||
"""Defines a WLED light."""
|
"""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__(
|
def __init__(
|
||||||
self, entry_id: str, coordinator: WLEDDataUpdateCoordinator, segment: int
|
self, entry_id: str, coordinator: WLEDDataUpdateCoordinator, segment: int
|
||||||
):
|
):
|
||||||
"""Initialize WLED light."""
|
"""Initialize WLED segment light."""
|
||||||
self._rgbw = coordinator.data.info.leds.rgbw
|
self._rgbw = coordinator.data.info.leds.rgbw
|
||||||
self._segment = segment
|
self._segment = segment
|
||||||
|
|
||||||
# Only apply the segment ID if it is not the first segment
|
# If this is the one and only segment, use a simpler name
|
||||||
name = coordinator.data.info.name
|
name = f"{coordinator.data.info.name} Segment {self._segment}"
|
||||||
if segment != 0:
|
if len(coordinator.data.state.segments) == 1:
|
||||||
name += f" {segment}"
|
name = coordinator.data.info.name
|
||||||
|
|
||||||
super().__init__(
|
super().__init__(
|
||||||
entry_id=entry_id,
|
entry_id=entry_id,
|
||||||
@@ -155,7 +213,16 @@ class WLEDLight(LightEntity, WLEDDeviceEntity):
|
|||||||
@property
|
@property
|
||||||
def brightness(self) -> Optional[int]:
|
def brightness(self) -> Optional[int]:
|
||||||
"""Return the brightness of this light between 1..255."""
|
"""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
|
@property
|
||||||
def white_value(self) -> Optional[int]:
|
def white_value(self) -> Optional[int]:
|
||||||
@@ -187,18 +254,30 @@ class WLEDLight(LightEntity, WLEDDeviceEntity):
|
|||||||
@property
|
@property
|
||||||
def is_on(self) -> bool:
|
def is_on(self) -> bool:
|
||||||
"""Return the state of the light."""
|
"""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
|
@wled_exception_handler
|
||||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||||
"""Turn off the light."""
|
"""Turn off the light."""
|
||||||
data = {ATTR_ON: False, ATTR_SEGMENT_ID: self._segment}
|
data = {ATTR_ON: False}
|
||||||
|
|
||||||
if ATTR_TRANSITION in kwargs:
|
if ATTR_TRANSITION in kwargs:
|
||||||
# WLED uses 100ms per unit, so 10 = 1 second.
|
# WLED uses 100ms per unit, so 10 = 1 second.
|
||||||
data[ATTR_TRANSITION] = round(kwargs[ATTR_TRANSITION] * 10)
|
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
|
@wled_exception_handler
|
||||||
async def async_turn_on(self, **kwargs: Any) -> None:
|
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||||
@@ -248,7 +327,23 @@ class WLEDLight(LightEntity, WLEDDeviceEntity):
|
|||||||
else:
|
else:
|
||||||
data[ATTR_COLOR_PRIMARY] += (self.white_value,)
|
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
|
@wled_exception_handler
|
||||||
async def async_effect(
|
async def async_effect(
|
||||||
@@ -273,45 +368,59 @@ class WLEDLight(LightEntity, WLEDDeviceEntity):
|
|||||||
if speed is not None:
|
if speed is not None:
|
||||||
data[ATTR_SPEED] = speed
|
data[ATTR_SPEED] = speed
|
||||||
|
|
||||||
await self.coordinator.wled.light(**data)
|
await self.coordinator.wled.segment(**data)
|
||||||
|
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def async_update_segments(
|
def async_update_segments(
|
||||||
entry: ConfigEntry,
|
entry: ConfigEntry,
|
||||||
coordinator: WLEDDataUpdateCoordinator,
|
coordinator: WLEDDataUpdateCoordinator,
|
||||||
current: Dict[int, WLEDLight],
|
current: Dict[int, WLEDSegmentLight],
|
||||||
async_add_entities,
|
async_add_entities,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Update segments."""
|
"""Update segments."""
|
||||||
segment_ids = {light.segment_id for light in coordinator.data.state.segments}
|
segment_ids = {light.segment_id for light in coordinator.data.state.segments}
|
||||||
current_ids = set(current)
|
current_ids = set(current)
|
||||||
|
|
||||||
# Process new segments, add them to Home Assistant
|
# Discard master (if present)
|
||||||
new_segments = []
|
current_ids.discard(-1)
|
||||||
for segment_id in segment_ids - current_ids:
|
|
||||||
current[segment_id] = WLEDLight(entry.entry_id, coordinator, segment_id)
|
|
||||||
new_segments.append(current[segment_id])
|
|
||||||
|
|
||||||
if new_segments:
|
# Process new segments, add them to Home Assistant
|
||||||
async_add_entities(new_segments)
|
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
|
# Process deleted segments, remove them from Home Assistant
|
||||||
for segment_id in current_ids - segment_ids:
|
for segment_id in current_ids - segment_ids:
|
||||||
coordinator.hass.async_create_task(
|
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(
|
async def async_remove_entity(
|
||||||
segment_id: int,
|
index: int,
|
||||||
coordinator: WLEDDataUpdateCoordinator,
|
coordinator: WLEDDataUpdateCoordinator,
|
||||||
current: Dict[int, WLEDLight],
|
current: Dict[int, WLEDSegmentLight],
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Remove WLED segment light from Home Assistant."""
|
"""Remove WLED segment light from Home Assistant."""
|
||||||
entity = current[segment_id]
|
entity = current[index]
|
||||||
await entity.async_remove()
|
await entity.async_remove()
|
||||||
registry = await async_get_entity_registry(coordinator.hass)
|
registry = await async_get_entity_registry(coordinator.hass)
|
||||||
if entity.entity_id in registry.entities:
|
if entity.entity_id in registry.entities:
|
||||||
registry.async_remove(entity.entity_id)
|
registry.async_remove(entity.entity_id)
|
||||||
del current[segment_id]
|
del current[index]
|
||||||
|
@@ -3,7 +3,7 @@
|
|||||||
"name": "WLED",
|
"name": "WLED",
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/wled",
|
"documentation": "https://www.home-assistant.io/integrations/wled",
|
||||||
"requirements": ["wled==0.4.1"],
|
"requirements": ["wled==0.4.2"],
|
||||||
"zeroconf": ["_wled._tcp.local."],
|
"zeroconf": ["_wled._tcp.local."],
|
||||||
"codeowners": ["@frenck"],
|
"codeowners": ["@frenck"],
|
||||||
"quality_scale": "platinum"
|
"quality_scale": "platinum"
|
||||||
|
@@ -7,7 +7,7 @@
|
|||||||
"bellows==0.16.2",
|
"bellows==0.16.2",
|
||||||
"pyserial==3.4",
|
"pyserial==3.4",
|
||||||
"zha-quirks==0.0.39",
|
"zha-quirks==0.0.39",
|
||||||
"zigpy-cc==0.4.2",
|
"zigpy-cc==0.4.4",
|
||||||
"zigpy-deconz==0.9.2",
|
"zigpy-deconz==0.9.2",
|
||||||
"zigpy==0.20.4",
|
"zigpy==0.20.4",
|
||||||
"zigpy-xbee==0.12.1",
|
"zigpy-xbee==0.12.1",
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
"""Constants used by Home Assistant components."""
|
"""Constants used by Home Assistant components."""
|
||||||
MAJOR_VERSION = 0
|
MAJOR_VERSION = 0
|
||||||
MINOR_VERSION = 111
|
MINOR_VERSION = 111
|
||||||
PATCH_VERSION = "0b2"
|
PATCH_VERSION = "0b4"
|
||||||
__short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
__short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
||||||
__version__ = f"{__short_version__}.{PATCH_VERSION}"
|
__version__ = f"{__short_version__}.{PATCH_VERSION}"
|
||||||
REQUIRED_PYTHON_VER = (3, 7, 0)
|
REQUIRED_PYTHON_VER = (3, 7, 0)
|
||||||
|
@@ -12,10 +12,10 @@ cryptography==2.9.2
|
|||||||
defusedxml==0.6.0
|
defusedxml==0.6.0
|
||||||
distro==1.5.0
|
distro==1.5.0
|
||||||
hass-nabucasa==0.34.5
|
hass-nabucasa==0.34.5
|
||||||
home-assistant-frontend==20200603.1
|
home-assistant-frontend==20200603.2
|
||||||
importlib-metadata==1.6.0
|
importlib-metadata==1.6.0
|
||||||
jinja2>=2.11.1
|
jinja2>=2.11.1
|
||||||
netdisco==2.6.0
|
netdisco==2.7.0
|
||||||
pip>=8.0.3
|
pip>=8.0.3
|
||||||
python-slugify==4.0.0
|
python-slugify==4.0.0
|
||||||
pytz>=2020.1
|
pytz>=2020.1
|
||||||
|
@@ -734,7 +734,7 @@ hole==0.5.1
|
|||||||
holidays==0.10.2
|
holidays==0.10.2
|
||||||
|
|
||||||
# homeassistant.components.frontend
|
# homeassistant.components.frontend
|
||||||
home-assistant-frontend==20200603.1
|
home-assistant-frontend==20200603.2
|
||||||
|
|
||||||
# homeassistant.components.zwave
|
# homeassistant.components.zwave
|
||||||
homeassistant-pyozw==0.1.10
|
homeassistant-pyozw==0.1.10
|
||||||
@@ -946,7 +946,7 @@ netdata==0.1.2
|
|||||||
|
|
||||||
# homeassistant.components.discovery
|
# homeassistant.components.discovery
|
||||||
# homeassistant.components.ssdp
|
# homeassistant.components.ssdp
|
||||||
netdisco==2.6.0
|
netdisco==2.7.0
|
||||||
|
|
||||||
# homeassistant.components.neurio_energy
|
# homeassistant.components.neurio_energy
|
||||||
neurio==0.3.1
|
neurio==0.3.1
|
||||||
@@ -1245,7 +1245,7 @@ pycfdns==0.0.1
|
|||||||
pychannels==1.0.0
|
pychannels==1.0.0
|
||||||
|
|
||||||
# homeassistant.components.cast
|
# homeassistant.components.cast
|
||||||
pychromecast==5.3.0
|
pychromecast==6.0.0
|
||||||
|
|
||||||
# homeassistant.components.cmus
|
# homeassistant.components.cmus
|
||||||
pycmus==0.1.1
|
pycmus==0.1.1
|
||||||
@@ -2195,7 +2195,7 @@ wirelesstagpy==0.4.0
|
|||||||
withings-api==2.1.3
|
withings-api==2.1.3
|
||||||
|
|
||||||
# homeassistant.components.wled
|
# homeassistant.components.wled
|
||||||
wled==0.4.1
|
wled==0.4.2
|
||||||
|
|
||||||
# homeassistant.components.xbee
|
# homeassistant.components.xbee
|
||||||
xbee-helper==0.0.7
|
xbee-helper==0.0.7
|
||||||
@@ -2251,7 +2251,7 @@ zhong_hong_hvac==1.0.9
|
|||||||
ziggo-mediabox-xl==1.1.0
|
ziggo-mediabox-xl==1.1.0
|
||||||
|
|
||||||
# homeassistant.components.zha
|
# homeassistant.components.zha
|
||||||
zigpy-cc==0.4.2
|
zigpy-cc==0.4.4
|
||||||
|
|
||||||
# homeassistant.components.zha
|
# homeassistant.components.zha
|
||||||
zigpy-deconz==0.9.2
|
zigpy-deconz==0.9.2
|
||||||
|
@@ -321,7 +321,7 @@ hole==0.5.1
|
|||||||
holidays==0.10.2
|
holidays==0.10.2
|
||||||
|
|
||||||
# homeassistant.components.frontend
|
# homeassistant.components.frontend
|
||||||
home-assistant-frontend==20200603.1
|
home-assistant-frontend==20200603.2
|
||||||
|
|
||||||
# homeassistant.components.zwave
|
# homeassistant.components.zwave
|
||||||
homeassistant-pyozw==0.1.10
|
homeassistant-pyozw==0.1.10
|
||||||
@@ -399,7 +399,7 @@ nessclient==0.9.15
|
|||||||
|
|
||||||
# homeassistant.components.discovery
|
# homeassistant.components.discovery
|
||||||
# homeassistant.components.ssdp
|
# homeassistant.components.ssdp
|
||||||
netdisco==2.6.0
|
netdisco==2.7.0
|
||||||
|
|
||||||
# homeassistant.components.nexia
|
# homeassistant.components.nexia
|
||||||
nexia==0.9.3
|
nexia==0.9.3
|
||||||
@@ -539,7 +539,7 @@ pyblackbird==0.5
|
|||||||
pybotvac==0.0.17
|
pybotvac==0.0.17
|
||||||
|
|
||||||
# homeassistant.components.cast
|
# homeassistant.components.cast
|
||||||
pychromecast==5.3.0
|
pychromecast==6.0.0
|
||||||
|
|
||||||
# homeassistant.components.coolmaster
|
# homeassistant.components.coolmaster
|
||||||
pycoolmasternet==0.0.4
|
pycoolmasternet==0.0.4
|
||||||
@@ -901,7 +901,7 @@ wiffi==1.0.0
|
|||||||
withings-api==2.1.3
|
withings-api==2.1.3
|
||||||
|
|
||||||
# homeassistant.components.wled
|
# homeassistant.components.wled
|
||||||
wled==0.4.1
|
wled==0.4.2
|
||||||
|
|
||||||
# homeassistant.components.bluesound
|
# homeassistant.components.bluesound
|
||||||
# homeassistant.components.rest
|
# homeassistant.components.rest
|
||||||
@@ -921,7 +921,7 @@ zeroconf==0.27.1
|
|||||||
zha-quirks==0.0.39
|
zha-quirks==0.0.39
|
||||||
|
|
||||||
# homeassistant.components.zha
|
# homeassistant.components.zha
|
||||||
zigpy-cc==0.4.2
|
zigpy-cc==0.4.4
|
||||||
|
|
||||||
# homeassistant.components.zha
|
# homeassistant.components.zha
|
||||||
zigpy-deconz==0.9.2
|
zigpy-deconz==0.9.2
|
||||||
|
@@ -80,17 +80,17 @@ async def async_setup_cast_internal_discovery(hass, config=None, discovery_info=
|
|||||||
browser = MagicMock(zc={})
|
browser = MagicMock(zc={})
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.cast.discovery.pychromecast.discovery.CastListener",
|
"homeassistant.components.cast.discovery.pychromecast.CastListener",
|
||||||
return_value=listener,
|
return_value=listener,
|
||||||
) as cast_listener, patch(
|
) as cast_listener, patch(
|
||||||
"homeassistant.components.cast.discovery.zeroconf.ServiceBrowser",
|
"homeassistant.components.cast.discovery.pychromecast.start_discovery",
|
||||||
return_value=browser,
|
return_value=browser,
|
||||||
):
|
) as start_discovery:
|
||||||
add_entities = await async_setup_cast(hass, config, discovery_info)
|
add_entities = await async_setup_cast(hass, config, discovery_info)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
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]
|
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",
|
"homeassistant.components.cast.discovery.pychromecast.get_chromecast_from_service",
|
||||||
return_value=chromecast,
|
return_value=chromecast,
|
||||||
) as get_chromecast, patch(
|
) as get_chromecast, patch(
|
||||||
"homeassistant.components.cast.discovery.pychromecast.discovery.CastListener",
|
"homeassistant.components.cast.discovery.pychromecast.CastListener",
|
||||||
return_value=listener,
|
return_value=listener,
|
||||||
) as cast_listener, patch(
|
) as cast_listener, patch(
|
||||||
"homeassistant.components.cast.discovery.zeroconf.ServiceBrowser",
|
"homeassistant.components.cast.discovery.pychromecast.start_discovery",
|
||||||
return_value=browser,
|
return_value=browser,
|
||||||
):
|
):
|
||||||
await async_setup_component(
|
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):
|
async def test_start_discovery_called_once(hass):
|
||||||
"""Test pychromecast.start_discovery called exactly once."""
|
"""Test pychromecast.start_discovery called exactly once."""
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.cast.discovery.pychromecast.discovery.CastListener",
|
"homeassistant.components.cast.discovery.pychromecast.start_discovery",
|
||||||
) as cast_listener, patch(
|
|
||||||
"homeassistant.components.cast.discovery.zeroconf.ServiceBrowser",
|
|
||||||
return_value=Mock(),
|
return_value=Mock(),
|
||||||
):
|
) as start_discovery:
|
||||||
await async_setup_cast(hass)
|
await async_setup_cast(hass)
|
||||||
|
|
||||||
assert cast_listener.call_count == 1
|
assert start_discovery.call_count == 1
|
||||||
|
|
||||||
await async_setup_cast(hass)
|
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):
|
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={})
|
browser = MagicMock(zc={})
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.cast.discovery.pychromecast.discovery.CastListener",
|
"homeassistant.components.cast.discovery.pychromecast.start_discovery",
|
||||||
) as cast_listener, patch(
|
|
||||||
"homeassistant.components.cast.discovery.zeroconf.ServiceBrowser",
|
|
||||||
return_value=browser,
|
return_value=browser,
|
||||||
):
|
) as start_discovery:
|
||||||
# start_discovery should be called with empty config
|
# start_discovery should be called with empty config
|
||||||
await async_setup_cast(hass, {})
|
await async_setup_cast(hass, {})
|
||||||
|
|
||||||
assert cast_listener.call_count == 1
|
assert start_discovery.call_count == 1
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.cast.discovery.pychromecast.stop_discovery"
|
"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)
|
stop_discovery.assert_called_once_with(browser)
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.cast.discovery.pychromecast.discovery.CastListener",
|
"homeassistant.components.cast.discovery.pychromecast.start_discovery",
|
||||||
) as cast_listener, patch(
|
|
||||||
"homeassistant.components.cast.discovery.zeroconf.ServiceBrowser",
|
|
||||||
return_value=browser,
|
return_value=browser,
|
||||||
):
|
) as start_discovery:
|
||||||
# start_discovery should be called again on re-startup
|
# start_discovery should be called again on re-startup
|
||||||
await async_setup_cast(hass)
|
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):
|
async def test_create_cast_device_without_uuid(hass):
|
||||||
|
@@ -1,12 +1,13 @@
|
|||||||
"""Fixtures for tests."""
|
"""Fixtures for tests."""
|
||||||
|
|
||||||
from mock import patch
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
from .common import ComponentFactory
|
from .common import ComponentFactory
|
||||||
|
|
||||||
|
from tests.async_mock import patch
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture()
|
@pytest.fixture()
|
||||||
def component_factory(hass: HomeAssistant):
|
def component_factory(hass: HomeAssistant):
|
||||||
|
@@ -4,8 +4,6 @@ import os
|
|||||||
import shutil
|
import shutil
|
||||||
from urllib.parse import urlencode
|
from urllib.parse import urlencode
|
||||||
|
|
||||||
from mock import Mock, patch
|
|
||||||
|
|
||||||
from homeassistant.components.media_player.const import (
|
from homeassistant.components.media_player.const import (
|
||||||
ATTR_MEDIA_CONTENT_ID,
|
ATTR_MEDIA_CONTENT_ID,
|
||||||
DOMAIN as DOMAIN_MP,
|
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.const import HTTP_INTERNAL_SERVER_ERROR
|
||||||
from homeassistant.setup import setup_component
|
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
|
from tests.common import assert_setup_component, get_test_home_assistant, mock_service
|
||||||
|
|
||||||
|
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from homeassistant.const import UNIT_PERCENTAGE
|
from homeassistant.const import STATE_UNKNOWN, UNIT_PERCENTAGE
|
||||||
from homeassistant.helpers import device_registry
|
from homeassistant.helpers import device_registry
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_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"
|
assert json["battery_state"]["error"]["code"] == "not_registered"
|
||||||
|
|
||||||
|
|
||||||
async def test_sensor_id_no_dupes(hass, create_registrations, webhook_client):
|
async def test_sensor_id_no_dupes(hass, create_registrations, webhook_client, caplog):
|
||||||
"""Test that sensors must have a unique ID."""
|
"""Test that a duplicate unique ID in registration updates the sensor."""
|
||||||
webhook_id = create_registrations[1]["webhook_id"]
|
webhook_id = create_registrations[1]["webhook_id"]
|
||||||
webhook_url = f"/api/webhook/{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()
|
reg_json = await reg_resp.json()
|
||||||
assert reg_json == {"success": True}
|
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)
|
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 "Re-register existing sensor" in caplog.text
|
||||||
assert dupe_json["success"] is False
|
|
||||||
assert dupe_json["error"]["code"] == "duplicate_unique_id"
|
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
|
import datetime
|
||||||
from typing import Union
|
from typing import Union
|
||||||
|
|
||||||
import mock
|
|
||||||
from py17track.package import Package
|
from py17track.package import Package
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
@@ -14,6 +13,7 @@ from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
|||||||
from homeassistant.setup import async_setup_component
|
from homeassistant.setup import async_setup_component
|
||||||
from homeassistant.util import utcnow
|
from homeassistant.util import utcnow
|
||||||
|
|
||||||
|
from tests.async_mock import MagicMock, patch
|
||||||
from tests.common import async_fire_time_changed
|
from tests.common import async_fire_time_changed
|
||||||
|
|
||||||
VALID_CONFIG_MINIMAL = {
|
VALID_CONFIG_MINIMAL = {
|
||||||
@@ -113,7 +113,7 @@ class ProfileMock:
|
|||||||
@pytest.fixture(autouse=True, name="mock_client")
|
@pytest.fixture(autouse=True, name="mock_client")
|
||||||
def fixture_mock_client():
|
def fixture_mock_client():
|
||||||
"""Mock py17track client."""
|
"""Mock py17track client."""
|
||||||
with mock.patch(
|
with patch(
|
||||||
"homeassistant.components.seventeentrack.sensor.SeventeenTrackClient",
|
"homeassistant.components.seventeentrack.sensor.SeventeenTrackClient",
|
||||||
new=ClientMock,
|
new=ClientMock,
|
||||||
):
|
):
|
||||||
@@ -137,7 +137,7 @@ async def _goto_future(hass, future=None):
|
|||||||
"""Move to future."""
|
"""Move to future."""
|
||||||
if not future:
|
if not future:
|
||||||
future = utcnow() + datetime.timedelta(minutes=10)
|
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)
|
async_fire_time_changed(hass, future)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
@@ -245,7 +245,7 @@ async def test_delivered_not_shown(hass):
|
|||||||
)
|
)
|
||||||
ProfileMock.package_list = [package]
|
ProfileMock.package_list = [package]
|
||||||
|
|
||||||
hass.components.persistent_notification = mock.MagicMock()
|
hass.components.persistent_notification = MagicMock()
|
||||||
await _setup_seventeentrack(hass, VALID_CONFIG_FULL_NO_DELIVERED)
|
await _setup_seventeentrack(hass, VALID_CONFIG_FULL_NO_DELIVERED)
|
||||||
assert not hass.states.async_entity_ids()
|
assert not hass.states.async_entity_ids()
|
||||||
hass.components.persistent_notification.create.assert_called()
|
hass.components.persistent_notification.create.assert_called()
|
||||||
@@ -258,7 +258,7 @@ async def test_delivered_shown(hass):
|
|||||||
)
|
)
|
||||||
ProfileMock.package_list = [package]
|
ProfileMock.package_list = [package]
|
||||||
|
|
||||||
hass.components.persistent_notification = mock.MagicMock()
|
hass.components.persistent_notification = MagicMock()
|
||||||
await _setup_seventeentrack(hass, VALID_CONFIG_FULL)
|
await _setup_seventeentrack(hass, VALID_CONFIG_FULL)
|
||||||
|
|
||||||
assert hass.states.get("sensor.seventeentrack_package_456") is not None
|
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]
|
ProfileMock.package_list = [package_delivered]
|
||||||
|
|
||||||
hass.components.persistent_notification = mock.MagicMock()
|
hass.components.persistent_notification = MagicMock()
|
||||||
await _goto_future(hass)
|
await _goto_future(hass)
|
||||||
|
|
||||||
hass.components.persistent_notification.create.assert_called()
|
hass.components.persistent_notification.create.assert_called()
|
||||||
|
@@ -2,13 +2,13 @@
|
|||||||
|
|
||||||
from typing import Callable, Dict, NamedTuple, Tuple
|
from typing import Callable, Dict, NamedTuple, Tuple
|
||||||
|
|
||||||
from mock import MagicMock
|
|
||||||
import pyvera as pv
|
import pyvera as pv
|
||||||
|
|
||||||
from homeassistant.components.vera.const import CONF_CONTROLLER, DOMAIN
|
from homeassistant.components.vera.const import CONF_CONTROLLER, DOMAIN
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.setup import async_setup_component
|
from homeassistant.setup import async_setup_component
|
||||||
|
|
||||||
|
from tests.async_mock import MagicMock
|
||||||
from tests.common import MockConfigEntry
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
SetupCallback = Callable[[pv.VeraController, dict], None]
|
SetupCallback = Callable[[pv.VeraController, dict], None]
|
||||||
|
@@ -1,10 +1,11 @@
|
|||||||
"""Fixtures for tests."""
|
"""Fixtures for tests."""
|
||||||
|
|
||||||
from mock import patch
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from .common import ComponentFactory
|
from .common import ComponentFactory
|
||||||
|
|
||||||
|
from tests.async_mock import patch
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture()
|
@pytest.fixture()
|
||||||
def vera_component_factory():
|
def vera_component_factory():
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
"""Vera tests."""
|
"""Vera tests."""
|
||||||
from mock import patch
|
|
||||||
from requests.exceptions import RequestException
|
from requests.exceptions import RequestException
|
||||||
|
|
||||||
from homeassistant import config_entries, data_entry_flow
|
from homeassistant import config_entries, data_entry_flow
|
||||||
@@ -12,7 +11,7 @@ from homeassistant.data_entry_flow import (
|
|||||||
RESULT_TYPE_FORM,
|
RESULT_TYPE_FORM,
|
||||||
)
|
)
|
||||||
|
|
||||||
from tests.async_mock import MagicMock
|
from tests.async_mock import MagicMock, patch
|
||||||
from tests.common import MockConfigEntry
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
|
|
||||||
|
@@ -29,6 +29,7 @@ from homeassistant.const import (
|
|||||||
ATTR_ICON,
|
ATTR_ICON,
|
||||||
SERVICE_TURN_OFF,
|
SERVICE_TURN_OFF,
|
||||||
SERVICE_TURN_ON,
|
SERVICE_TURN_ON,
|
||||||
|
STATE_OFF,
|
||||||
STATE_ON,
|
STATE_ON,
|
||||||
STATE_UNAVAILABLE,
|
STATE_UNAVAILABLE,
|
||||||
)
|
)
|
||||||
@@ -50,7 +51,7 @@ async def test_rgb_light_state(
|
|||||||
entity_registry = await hass.helpers.entity_registry.async_get_registry()
|
entity_registry = await hass.helpers.entity_registry.async_get_registry()
|
||||||
|
|
||||||
# First segment of the strip
|
# 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
|
||||||
assert state.attributes.get(ATTR_BRIGHTNESS) == 127
|
assert state.attributes.get(ATTR_BRIGHTNESS) == 127
|
||||||
assert state.attributes.get(ATTR_EFFECT) == "Solid"
|
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.attributes.get(ATTR_SPEED) == 32
|
||||||
assert state.state == STATE_ON
|
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
|
||||||
assert entry.unique_id == "aabbccddeeff_0"
|
assert entry.unique_id == "aabbccddeeff_0"
|
||||||
|
|
||||||
# Second segment of the strip
|
# 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
|
||||||
assert state.attributes.get(ATTR_BRIGHTNESS) == 127
|
assert state.attributes.get(ATTR_BRIGHTNESS) == 127
|
||||||
assert state.attributes.get(ATTR_EFFECT) == "Blink"
|
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.attributes.get(ATTR_SPEED) == 16
|
||||||
assert state.state == STATE_ON
|
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
|
||||||
assert entry.unique_id == "aabbccddeeff_1"
|
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
|
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker, caplog
|
||||||
) -> None:
|
) -> 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)
|
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(
|
await hass.services.async_call(
|
||||||
LIGHT_DOMAIN,
|
LIGHT_DOMAIN,
|
||||||
SERVICE_TURN_OFF,
|
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,
|
blocking=True,
|
||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
@@ -106,14 +117,14 @@ async def test_switch_change_state(
|
|||||||
on=False, segment_id=0, transition=50,
|
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(
|
await hass.services.async_call(
|
||||||
LIGHT_DOMAIN,
|
LIGHT_DOMAIN,
|
||||||
SERVICE_TURN_ON,
|
SERVICE_TURN_ON,
|
||||||
{
|
{
|
||||||
ATTR_BRIGHTNESS: 42,
|
ATTR_BRIGHTNESS: 42,
|
||||||
ATTR_EFFECT: "Chase",
|
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_RGB_COLOR: [255, 0, 0],
|
||||||
ATTR_TRANSITION: 5,
|
ATTR_TRANSITION: 5,
|
||||||
},
|
},
|
||||||
@@ -129,11 +140,11 @@ async def test_switch_change_state(
|
|||||||
transition=50,
|
transition=50,
|
||||||
)
|
)
|
||||||
|
|
||||||
with patch("wled.WLED.light") as light_mock:
|
with patch("wled.WLED.segment") as light_mock:
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
LIGHT_DOMAIN,
|
LIGHT_DOMAIN,
|
||||||
SERVICE_TURN_ON,
|
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,
|
blocking=True,
|
||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
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(
|
async def test_dynamically_handle_segments(
|
||||||
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker
|
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test if a new/deleted segment is dynamically added/removed."""
|
"""Test if a new/deleted segment is dynamically added/removed."""
|
||||||
await init_integration(hass, aioclient_mock)
|
await init_integration(hass, aioclient_mock)
|
||||||
|
|
||||||
assert hass.states.get("light.wled_rgb_light")
|
assert hass.states.get("light.wled_rgb_light_master")
|
||||||
assert hass.states.get("light.wled_rgb_light_1")
|
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"))
|
data = json.loads(load_fixture("wled/rgb_single_segment.json"))
|
||||||
device = WLEDDevice(data)
|
device = WLEDDevice(data)
|
||||||
|
|
||||||
# Test removal if segment went missing
|
# Test removal if segment went missing, including the master entity
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.wled.WLED.update", return_value=device,
|
"homeassistant.components.wled.WLED.update", return_value=device,
|
||||||
):
|
):
|
||||||
async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL)
|
async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert hass.states.get("light.wled_rgb_light")
|
assert hass.states.get("light.wled_rgb_light_segment_0")
|
||||||
assert not hass.states.get("light.wled_rgb_light_1")
|
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)
|
async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert hass.states.get("light.wled_rgb_light")
|
assert hass.states.get("light.wled_rgb_light_master")
|
||||||
assert hass.states.get("light.wled_rgb_light_1")
|
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(
|
async def test_light_error(
|
||||||
@@ -182,12 +338,12 @@ async def test_light_error(
|
|||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
LIGHT_DOMAIN,
|
LIGHT_DOMAIN,
|
||||||
SERVICE_TURN_OFF,
|
SERVICE_TURN_OFF,
|
||||||
{ATTR_ENTITY_ID: "light.wled_rgb_light"},
|
{ATTR_ENTITY_ID: "light.wled_rgb_light_segment_0"},
|
||||||
blocking=True,
|
blocking=True,
|
||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
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 state.state == STATE_ON
|
||||||
assert "Invalid response from API" in caplog.text
|
assert "Invalid response from API" in caplog.text
|
||||||
|
|
||||||
@@ -199,17 +355,17 @@ async def test_light_connection_error(
|
|||||||
await init_integration(hass, aioclient_mock)
|
await init_integration(hass, aioclient_mock)
|
||||||
|
|
||||||
with patch("homeassistant.components.wled.WLED.update"), patch(
|
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(
|
await hass.services.async_call(
|
||||||
LIGHT_DOMAIN,
|
LIGHT_DOMAIN,
|
||||||
SERVICE_TURN_OFF,
|
SERVICE_TURN_OFF,
|
||||||
{ATTR_ENTITY_ID: "light.wled_rgb_light"},
|
{ATTR_ENTITY_ID: "light.wled_rgb_light_segment_0"},
|
||||||
blocking=True,
|
blocking=True,
|
||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
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
|
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_HS_COLOR) == (0.0, 100.0)
|
||||||
assert state.attributes.get(ATTR_WHITE_VALUE) == 139
|
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(
|
await hass.services.async_call(
|
||||||
LIGHT_DOMAIN,
|
LIGHT_DOMAIN,
|
||||||
SERVICE_TURN_ON,
|
SERVICE_TURN_ON,
|
||||||
@@ -236,7 +392,7 @@ async def test_rgbw_light(
|
|||||||
on=True, segment_id=0, color_primary=(255, 159, 70, 139),
|
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(
|
await hass.services.async_call(
|
||||||
LIGHT_DOMAIN,
|
LIGHT_DOMAIN,
|
||||||
SERVICE_TURN_ON,
|
SERVICE_TURN_ON,
|
||||||
@@ -248,7 +404,7 @@ async def test_rgbw_light(
|
|||||||
color_primary=(255, 0, 0, 100), on=True, segment_id=0,
|
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(
|
await hass.services.async_call(
|
||||||
LIGHT_DOMAIN,
|
LIGHT_DOMAIN,
|
||||||
SERVICE_TURN_ON,
|
SERVICE_TURN_ON,
|
||||||
@@ -271,13 +427,13 @@ async def test_effect_service(
|
|||||||
"""Test the effect service of a WLED light."""
|
"""Test the effect service of a WLED light."""
|
||||||
await init_integration(hass, aioclient_mock)
|
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(
|
await hass.services.async_call(
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
SERVICE_EFFECT,
|
SERVICE_EFFECT,
|
||||||
{
|
{
|
||||||
ATTR_EFFECT: "Rainbow",
|
ATTR_EFFECT: "Rainbow",
|
||||||
ATTR_ENTITY_ID: "light.wled_rgb_light",
|
ATTR_ENTITY_ID: "light.wled_rgb_light_segment_0",
|
||||||
ATTR_INTENSITY: 200,
|
ATTR_INTENSITY: 200,
|
||||||
ATTR_REVERSE: True,
|
ATTR_REVERSE: True,
|
||||||
ATTR_SPEED: 100,
|
ATTR_SPEED: 100,
|
||||||
@@ -289,11 +445,11 @@ async def test_effect_service(
|
|||||||
effect="Rainbow", intensity=200, reverse=True, segment_id=0, speed=100,
|
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(
|
await hass.services.async_call(
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
SERVICE_EFFECT,
|
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,
|
blocking=True,
|
||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
@@ -301,12 +457,12 @@ async def test_effect_service(
|
|||||||
segment_id=0, effect=9,
|
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(
|
await hass.services.async_call(
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
SERVICE_EFFECT,
|
SERVICE_EFFECT,
|
||||||
{
|
{
|
||||||
ATTR_ENTITY_ID: "light.wled_rgb_light",
|
ATTR_ENTITY_ID: "light.wled_rgb_light_segment_0",
|
||||||
ATTR_INTENSITY: 200,
|
ATTR_INTENSITY: 200,
|
||||||
ATTR_REVERSE: True,
|
ATTR_REVERSE: True,
|
||||||
ATTR_SPEED: 100,
|
ATTR_SPEED: 100,
|
||||||
@@ -318,13 +474,13 @@ async def test_effect_service(
|
|||||||
intensity=200, reverse=True, segment_id=0, speed=100,
|
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(
|
await hass.services.async_call(
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
SERVICE_EFFECT,
|
SERVICE_EFFECT,
|
||||||
{
|
{
|
||||||
ATTR_EFFECT: "Rainbow",
|
ATTR_EFFECT: "Rainbow",
|
||||||
ATTR_ENTITY_ID: "light.wled_rgb_light",
|
ATTR_ENTITY_ID: "light.wled_rgb_light_segment_0",
|
||||||
ATTR_REVERSE: True,
|
ATTR_REVERSE: True,
|
||||||
ATTR_SPEED: 100,
|
ATTR_SPEED: 100,
|
||||||
},
|
},
|
||||||
@@ -335,13 +491,13 @@ async def test_effect_service(
|
|||||||
effect="Rainbow", reverse=True, segment_id=0, speed=100,
|
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(
|
await hass.services.async_call(
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
SERVICE_EFFECT,
|
SERVICE_EFFECT,
|
||||||
{
|
{
|
||||||
ATTR_EFFECT: "Rainbow",
|
ATTR_EFFECT: "Rainbow",
|
||||||
ATTR_ENTITY_ID: "light.wled_rgb_light",
|
ATTR_ENTITY_ID: "light.wled_rgb_light_segment_0",
|
||||||
ATTR_INTENSITY: 200,
|
ATTR_INTENSITY: 200,
|
||||||
ATTR_SPEED: 100,
|
ATTR_SPEED: 100,
|
||||||
},
|
},
|
||||||
@@ -352,13 +508,13 @@ async def test_effect_service(
|
|||||||
effect="Rainbow", intensity=200, segment_id=0, speed=100,
|
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(
|
await hass.services.async_call(
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
SERVICE_EFFECT,
|
SERVICE_EFFECT,
|
||||||
{
|
{
|
||||||
ATTR_EFFECT: "Rainbow",
|
ATTR_EFFECT: "Rainbow",
|
||||||
ATTR_ENTITY_ID: "light.wled_rgb_light",
|
ATTR_ENTITY_ID: "light.wled_rgb_light_segment_0",
|
||||||
ATTR_INTENSITY: 200,
|
ATTR_INTENSITY: 200,
|
||||||
ATTR_REVERSE: True,
|
ATTR_REVERSE: True,
|
||||||
},
|
},
|
||||||
@@ -381,11 +537,11 @@ async def test_effect_service_error(
|
|||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
SERVICE_EFFECT,
|
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,
|
blocking=True,
|
||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
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 state.state == STATE_ON
|
||||||
assert "Invalid response from API" in caplog.text
|
assert "Invalid response from API" in caplog.text
|
||||||
|
Reference in New Issue
Block a user