Add Twinkly effects (#82861)
* Add Twinkly effects * Remove spurious comment
This commit is contained in:
parent
9f8dea10f7
commit
33cd59d3c2
6 changed files with 112 additions and 8 deletions
|
@ -2,6 +2,7 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from collections.abc import Mapping
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
|
@ -10,10 +11,12 @@ from ttls.client import Twinkly
|
|||
|
||||
from homeassistant.components.light import (
|
||||
ATTR_BRIGHTNESS,
|
||||
ATTR_EFFECT,
|
||||
ATTR_RGB_COLOR,
|
||||
ATTR_RGBW_COLOR,
|
||||
ColorMode,
|
||||
LightEntity,
|
||||
LightEntityFeature,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_MODEL
|
||||
|
@ -91,6 +94,8 @@ class TwinklyLight(LightEntity):
|
|||
self._is_on = False
|
||||
self._is_available = False
|
||||
self._attributes: dict[Any, Any] = {}
|
||||
self._current_movie: dict[Any, Any] = {}
|
||||
self._movies: list[Any] = []
|
||||
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
|
@ -127,19 +132,41 @@ class TwinklyLight(LightEntity):
|
|||
name=self.name,
|
||||
)
|
||||
|
||||
@property
|
||||
def supported_features(self) -> LightEntityFeature:
|
||||
"""Return supported features."""
|
||||
return LightEntityFeature.EFFECT
|
||||
|
||||
@property
|
||||
def is_on(self) -> bool:
|
||||
"""Return true if light is on."""
|
||||
return self._is_on
|
||||
|
||||
@property
|
||||
def extra_state_attributes(self) -> dict:
|
||||
def extra_state_attributes(self) -> Mapping[str, Any]:
|
||||
"""Return device specific state attributes."""
|
||||
|
||||
attributes = self._attributes
|
||||
|
||||
return attributes
|
||||
|
||||
@property
|
||||
def effect(self) -> str | None:
|
||||
"""Return the current effect."""
|
||||
if "name" in self._current_movie:
|
||||
_LOGGER.debug("Current effect '%s'", self._current_movie["name"])
|
||||
return f"{self._current_movie['id']} {self._current_movie['name']}"
|
||||
return None
|
||||
|
||||
@property
|
||||
def effect_list(self) -> list[str]:
|
||||
"""Return the list of saved effects."""
|
||||
effect_list = []
|
||||
for movie in self._movies:
|
||||
effect_list.append(f"{movie['id']} {movie['name']}")
|
||||
_LOGGER.debug("Effect list '%s'", effect_list)
|
||||
return effect_list
|
||||
|
||||
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||
"""Turn device on."""
|
||||
if ATTR_BRIGHTNESS in kwargs:
|
||||
|
@ -160,15 +187,16 @@ class TwinklyLight(LightEntity):
|
|||
if isinstance(self._attr_rgbw_color, tuple):
|
||||
|
||||
await self._client.interview()
|
||||
# Reagarrange from rgbw to wrgb
|
||||
# Static color only supports rgb
|
||||
await self._client.set_static_colour(
|
||||
(
|
||||
self._attr_rgbw_color[3],
|
||||
self._attr_rgbw_color[0],
|
||||
self._attr_rgbw_color[1],
|
||||
self._attr_rgbw_color[2],
|
||||
)
|
||||
)
|
||||
await self._client.set_mode("color")
|
||||
self._client.default_mode = "color"
|
||||
|
||||
if ATTR_RGB_COLOR in kwargs:
|
||||
if kwargs[ATTR_RGB_COLOR] != self._attr_rgb_color:
|
||||
|
@ -177,9 +205,20 @@ class TwinklyLight(LightEntity):
|
|||
if isinstance(self._attr_rgb_color, tuple):
|
||||
|
||||
await self._client.interview()
|
||||
# Reagarrange from rgbw to wrgb
|
||||
await self._client.set_static_colour(self._attr_rgb_color)
|
||||
await self._client.set_mode("color")
|
||||
self._client.default_mode = "color"
|
||||
|
||||
if ATTR_EFFECT in kwargs:
|
||||
_LOGGER.debug("Setting effect '%s'", kwargs[ATTR_EFFECT])
|
||||
movie_id = kwargs[ATTR_EFFECT].split(" ")[0]
|
||||
if "id" not in self._current_movie or int(movie_id) != int(
|
||||
self._current_movie["id"]
|
||||
):
|
||||
await self._client.interview()
|
||||
await self._client.set_current_movie(int(movie_id))
|
||||
await self._client.set_mode("movie")
|
||||
self._client.default_mode = "movie"
|
||||
if not self._is_on:
|
||||
await self._client.turn_on()
|
||||
|
||||
|
@ -232,6 +271,9 @@ class TwinklyLight(LightEntity):
|
|||
if key not in HIDDEN_DEV_VALUES:
|
||||
self._attributes[key] = value
|
||||
|
||||
await self.async_update_movies()
|
||||
await self.async_update_current_movie()
|
||||
|
||||
if not self._is_available:
|
||||
_LOGGER.info("Twinkly '%s' is now available", self._client.host)
|
||||
|
||||
|
@ -245,3 +287,16 @@ class TwinklyLight(LightEntity):
|
|||
"Twinkly '%s' is not reachable (client error)", self._client.host
|
||||
)
|
||||
self._is_available = False
|
||||
|
||||
async def async_update_movies(self) -> None:
|
||||
"""Update the list of movies (effects)."""
|
||||
movies = await self._client.get_saved_movies()
|
||||
_LOGGER.debug("Movies: %s", movies)
|
||||
if "movies" in movies:
|
||||
self._movies = movies["movies"]
|
||||
|
||||
async def async_update_current_movie(self) -> None:
|
||||
"""Update the current active movie."""
|
||||
current_movie = await self._client.get_current_movie()
|
||||
if "id" in current_movie:
|
||||
self._current_movie = current_movie
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"domain": "twinkly",
|
||||
"name": "Twinkly",
|
||||
"documentation": "https://www.home-assistant.io/integrations/twinkly",
|
||||
"requirements": ["ttls==1.4.3"],
|
||||
"requirements": ["ttls==1.5.1"],
|
||||
"codeowners": ["@dr1rrb", "@Robbie1221"],
|
||||
"config_flow": true,
|
||||
"dhcp": [{ "hostname": "twinkly_*" }],
|
||||
|
|
|
@ -2457,7 +2457,7 @@ tp-connected==0.0.4
|
|||
transmissionrpc==0.11
|
||||
|
||||
# homeassistant.components.twinkly
|
||||
ttls==1.4.3
|
||||
ttls==1.5.1
|
||||
|
||||
# homeassistant.components.tuya
|
||||
tuya-iot-py-sdk==0.6.6
|
||||
|
|
|
@ -1694,7 +1694,7 @@ total_connect_client==2022.10
|
|||
transmissionrpc==0.11
|
||||
|
||||
# homeassistant.components.twinkly
|
||||
ttls==1.4.3
|
||||
ttls==1.5.1
|
||||
|
||||
# homeassistant.components.tuya
|
||||
tuya-iot-py-sdk==0.6.6
|
||||
|
|
|
@ -22,6 +22,9 @@ class ClientMock:
|
|||
self.state = True
|
||||
self.brightness = {"mode": "enabled", "value": 10}
|
||||
self.color = None
|
||||
self.movies = [{"id": 1, "name": "Rainbow"}, {"id": 2, "name": "Flare"}]
|
||||
self.current_movie = {}
|
||||
self.default_mode = "movie"
|
||||
|
||||
self.id = str(uuid4())
|
||||
self.device_info = {
|
||||
|
@ -81,3 +84,22 @@ class ClientMock:
|
|||
|
||||
async def interview(self) -> None:
|
||||
"""Interview."""
|
||||
|
||||
async def get_saved_movies(self) -> dict:
|
||||
"""Get saved movies."""
|
||||
return self.movies
|
||||
|
||||
async def get_current_movie(self) -> dict:
|
||||
"""Get current movie."""
|
||||
return self.current_movie
|
||||
|
||||
async def set_current_movie(self, movie_id: int) -> dict:
|
||||
"""Set current movie."""
|
||||
self.current_movie = {"id": movie_id}
|
||||
|
||||
async def set_mode(self, mode: str) -> None:
|
||||
"""Set mode."""
|
||||
if mode == "off":
|
||||
self.turn_off
|
||||
else:
|
||||
self.turn_on
|
||||
|
|
|
@ -118,7 +118,8 @@ async def test_turn_on_with_color_rgbw(hass: HomeAssistant):
|
|||
state = hass.states.get(entity.entity_id)
|
||||
|
||||
assert state.state == "on"
|
||||
assert client.color == (0, 128, 64, 32)
|
||||
assert client.color == (128, 64, 32)
|
||||
assert client.default_mode == "color"
|
||||
|
||||
|
||||
async def test_turn_on_with_color_rgb(hass: HomeAssistant):
|
||||
|
@ -142,6 +143,32 @@ async def test_turn_on_with_color_rgb(hass: HomeAssistant):
|
|||
|
||||
assert state.state == "on"
|
||||
assert client.color == (128, 64, 32)
|
||||
assert client.default_mode == "color"
|
||||
|
||||
|
||||
async def test_turn_on_with_effect(hass: HomeAssistant):
|
||||
"""Test support of the light.turn_on service with a brightness parameter."""
|
||||
client = ClientMock()
|
||||
client.state = False
|
||||
client.device_info["led_profile"] = "RGB"
|
||||
client.brightness = {"mode": "enabled", "value": 255}
|
||||
entity, _, _, _ = await _create_entries(hass, client)
|
||||
|
||||
assert hass.states.get(entity.entity_id).state == "off"
|
||||
assert client.current_movie == {}
|
||||
|
||||
await hass.services.async_call(
|
||||
"light",
|
||||
"turn_on",
|
||||
service_data={"entity_id": entity.entity_id, "effect": "1 Rainbow"},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(entity.entity_id)
|
||||
|
||||
assert state.state == "on"
|
||||
assert client.current_movie["id"] == 1
|
||||
assert client.default_mode == "movie"
|
||||
|
||||
|
||||
async def test_turn_off(hass: HomeAssistant):
|
||||
|
|
Loading…
Add table
Reference in a new issue