mirror of
https://github.com/home-assistant/core.git
synced 2025-07-16 09:47:13 +00:00
Add mode selector to Twinkly (#134041)
This commit is contained in:
parent
add4e1a708
commit
0b32342bf0
@ -14,7 +14,7 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from .const import DOMAIN
|
||||
from .coordinator import TwinklyCoordinator
|
||||
|
||||
PLATFORMS = [Platform.LIGHT]
|
||||
PLATFORMS = [Platform.LIGHT, Platform.SELECT]
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
@ -27,6 +27,7 @@ class TwinklyData:
|
||||
is_on: bool
|
||||
movies: dict[int, str]
|
||||
current_movie: int | None
|
||||
current_mode: str | None
|
||||
|
||||
|
||||
class TwinklyCoordinator(DataUpdateCoordinator[TwinklyData]):
|
||||
@ -66,6 +67,8 @@ class TwinklyCoordinator(DataUpdateCoordinator[TwinklyData]):
|
||||
device_info = await self.client.get_details()
|
||||
brightness = await self.client.get_brightness()
|
||||
is_on = await self.client.is_on()
|
||||
mode_data = await self.client.get_mode()
|
||||
current_mode = mode_data.get("mode")
|
||||
if self.supports_effects:
|
||||
movies = (await self.client.get_saved_movies())["movies"]
|
||||
except (TimeoutError, ClientError) as exception:
|
||||
@ -87,6 +90,7 @@ class TwinklyCoordinator(DataUpdateCoordinator[TwinklyData]):
|
||||
is_on,
|
||||
{movie["id"]: movie["name"] for movie in movies},
|
||||
current_movie.get("id"),
|
||||
current_mode,
|
||||
)
|
||||
|
||||
def _async_update_device_info(self, name: str) -> None:
|
||||
|
49
homeassistant/components/twinkly/select.py
Normal file
49
homeassistant/components/twinkly/select.py
Normal file
@ -0,0 +1,49 @@
|
||||
"""The Twinkly select component."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
||||
from ttls.client import TWINKLY_MODES
|
||||
|
||||
from homeassistant.components.select import SelectEntity
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from . import TwinklyConfigEntry, TwinklyCoordinator
|
||||
from .entity import TwinklyEntity
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: TwinklyConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up a mode select from a config entry."""
|
||||
entity = TwinklyModeSelect(config_entry.runtime_data)
|
||||
async_add_entities([entity], update_before_add=True)
|
||||
|
||||
|
||||
class TwinklyModeSelect(TwinklyEntity, SelectEntity):
|
||||
"""Twinkly Mode Selection."""
|
||||
|
||||
_attr_name = "Mode"
|
||||
_attr_options = TWINKLY_MODES
|
||||
|
||||
def __init__(self, coordinator: TwinklyCoordinator) -> None:
|
||||
"""Initialize TwinklyModeSelect."""
|
||||
super().__init__(coordinator)
|
||||
self._attr_unique_id = f"{coordinator.data.device_info["mac"]}_mode"
|
||||
self.client = coordinator.client
|
||||
|
||||
@property
|
||||
def current_option(self) -> str | None:
|
||||
"""Return current mode."""
|
||||
return self.coordinator.data.current_mode
|
||||
|
||||
async def async_select_option(self, option: str) -> None:
|
||||
"""Change the selected option."""
|
||||
await self.client.set_mode(option)
|
||||
await self.coordinator.async_refresh()
|
@ -57,6 +57,7 @@ def mock_twinkly_client() -> Generator[AsyncMock]:
|
||||
client.get_current_movie.return_value = load_json_object_fixture(
|
||||
"get_current_movie.json", DOMAIN
|
||||
)
|
||||
client.get_mode.return_value = load_json_object_fixture("get_mode.json", DOMAIN)
|
||||
client.is_on.return_value = True
|
||||
client.get_brightness.return_value = {"mode": "enabled", "value": 10}
|
||||
client.host = "192.168.0.123"
|
||||
|
3
tests/components/twinkly/fixtures/get_mode.json
Normal file
3
tests/components/twinkly/fixtures/get_mode.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"mode": "color"
|
||||
}
|
66
tests/components/twinkly/snapshots/test_select.ambr
Normal file
66
tests/components/twinkly/snapshots/test_select.ambr
Normal file
@ -0,0 +1,66 @@
|
||||
# serializer version: 1
|
||||
# name: test_select_entities[select.tree_1_mode-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
'color',
|
||||
'demo',
|
||||
'effect',
|
||||
'movie',
|
||||
'off',
|
||||
'playlist',
|
||||
'rt',
|
||||
]),
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'select',
|
||||
'entity_category': None,
|
||||
'entity_id': 'select.tree_1_mode',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Mode',
|
||||
'platform': 'twinkly',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '00:2d:13:3b:aa:bb_mode',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_select_entities[select.tree_1_mode-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Tree 1 Mode',
|
||||
'options': list([
|
||||
'color',
|
||||
'demo',
|
||||
'effect',
|
||||
'movie',
|
||||
'off',
|
||||
'playlist',
|
||||
'rt',
|
||||
]),
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'select.tree_1_mode',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'color',
|
||||
})
|
||||
# ---
|
77
tests/components/twinkly/test_select.py
Normal file
77
tests/components/twinkly/test_select.py
Normal file
@ -0,0 +1,77 @@
|
||||
"""Tests for the Twinkly select component."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import timedelta
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
from freezegun.api import FrozenDateTimeFactory
|
||||
import pytest
|
||||
from syrupy import SnapshotAssertion
|
||||
|
||||
from homeassistant.components.select import DOMAIN as SELECT_DOMAIN
|
||||
from homeassistant.const import ATTR_ENTITY_ID, ATTR_OPTION, STATE_UNAVAILABLE, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
|
||||
from . import setup_integration
|
||||
|
||||
from tests.common import MockConfigEntry, async_fire_time_changed, snapshot_platform
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("mock_twinkly_client")
|
||||
async def test_select_entities(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
entity_registry: er.EntityRegistry,
|
||||
snapshot: SnapshotAssertion,
|
||||
) -> None:
|
||||
"""Test the created select entities."""
|
||||
with patch("homeassistant.components.twinkly.PLATFORMS", [Platform.SELECT]):
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
|
||||
|
||||
|
||||
async def test_select_mode(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_twinkly_client: AsyncMock,
|
||||
) -> None:
|
||||
"""Test selecting a mode."""
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
state = hass.states.get("select.tree_1_mode")
|
||||
assert state is not None
|
||||
assert state.state == "color"
|
||||
|
||||
await hass.services.async_call(
|
||||
SELECT_DOMAIN,
|
||||
"select_option",
|
||||
{
|
||||
ATTR_ENTITY_ID: "select.tree_1_mode",
|
||||
ATTR_OPTION: "movie",
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
mock_twinkly_client.set_mode.assert_called_once_with("movie")
|
||||
mock_twinkly_client.interview.assert_not_called()
|
||||
|
||||
|
||||
async def test_mode_unavailable(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_twinkly_client: AsyncMock,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
) -> None:
|
||||
"""Test handling of unavailable mode data."""
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
mock_twinkly_client.get_mode.side_effect = Exception
|
||||
freezer.tick(timedelta(seconds=30))
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("select.tree_1_mode")
|
||||
assert state.state == STATE_UNAVAILABLE
|
Loading…
x
Reference in New Issue
Block a user