Skip to content

Commit 4d525de

Browse files
MoonDevLTdavet2001
andauthored
Add dimming functionality to the Lunatone light entity (home-assistant#154508)
Co-authored-by: Dave T <[email protected]>
1 parent c38e020 commit 4d525de

File tree

3 files changed

+109
-7
lines changed

3 files changed

+109
-7
lines changed

homeassistant/components/lunatone/light.py

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,17 @@
55
import asyncio
66
from typing import Any
77

8-
from homeassistant.components.light import ColorMode, LightEntity
8+
from homeassistant.components.light import (
9+
ATTR_BRIGHTNESS,
10+
ColorMode,
11+
LightEntity,
12+
brightness_supported,
13+
)
914
from homeassistant.core import HomeAssistant, callback
1015
from homeassistant.helpers.device_registry import DeviceInfo
1116
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
1217
from homeassistant.helpers.update_coordinator import CoordinatorEntity
18+
from homeassistant.util.color import brightness_to_value, value_to_brightness
1319

1420
from .const import DOMAIN
1521
from .coordinator import LunatoneConfigEntry, LunatoneDevicesDataUpdateCoordinator
@@ -42,8 +48,10 @@ class LunatoneLight(
4248
):
4349
"""Representation of a Lunatone light."""
4450

45-
_attr_color_mode = ColorMode.ONOFF
46-
_attr_supported_color_modes = {ColorMode.ONOFF}
51+
BRIGHTNESS_SCALE = (1, 100)
52+
53+
_last_brightness = 255
54+
4755
_attr_has_entity_name = True
4856
_attr_name = None
4957
_attr_should_poll = False
@@ -82,6 +90,25 @@ def is_on(self) -> bool:
8290
"""Return True if light is on."""
8391
return self._device is not None and self._device.is_on
8492

93+
@property
94+
def brightness(self) -> int:
95+
"""Return the brightness of this light between 0..255."""
96+
if self._device is None:
97+
return 0
98+
return value_to_brightness(self.BRIGHTNESS_SCALE, self._device.brightness)
99+
100+
@property
101+
def color_mode(self) -> ColorMode:
102+
"""Return the color mode of the light."""
103+
if self._device is not None and self._device.is_dimmable:
104+
return ColorMode.BRIGHTNESS
105+
return ColorMode.ONOFF
106+
107+
@property
108+
def supported_color_modes(self) -> set[ColorMode]:
109+
"""Return the supported color modes."""
110+
return {self.color_mode}
111+
85112
@callback
86113
def _handle_coordinator_update(self) -> None:
87114
"""Handle updated data from the coordinator."""
@@ -91,13 +118,27 @@ def _handle_coordinator_update(self) -> None:
91118
async def async_turn_on(self, **kwargs: Any) -> None:
92119
"""Instruct the light to turn on."""
93120
assert self._device
94-
await self._device.switch_on()
121+
122+
if brightness_supported(self.supported_color_modes):
123+
brightness = kwargs.get(ATTR_BRIGHTNESS, self._last_brightness)
124+
await self._device.fade_to_brightness(
125+
brightness_to_value(self.BRIGHTNESS_SCALE, brightness)
126+
)
127+
else:
128+
await self._device.switch_on()
129+
95130
await asyncio.sleep(STATUS_UPDATE_DELAY)
96131
await self.coordinator.async_refresh()
97132

98133
async def async_turn_off(self, **kwargs: Any) -> None:
99134
"""Instruct the light to turn off."""
100135
assert self._device
101-
await self._device.switch_off()
136+
137+
if brightness_supported(self.supported_color_modes):
138+
self._last_brightness = self.brightness
139+
await self._device.fade_to_brightness(0)
140+
else:
141+
await self._device.switch_off()
142+
102143
await asyncio.sleep(STATUS_UPDATE_DELAY)
103144
await self.coordinator.async_refresh()

tests/components/lunatone/conftest.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ def mock_setup_entry() -> Generator[AsyncMock]:
2727
@pytest.fixture
2828
def mock_lunatone_devices() -> Generator[AsyncMock]:
2929
"""Mock a Lunatone devices object."""
30+
state = {"is_dimmable": False}
3031

3132
def build_devices_mock(devices: Devices):
3233
device_list = []
@@ -36,6 +37,10 @@ def build_devices_mock(devices: Devices):
3637
device.id = device.data.id
3738
device.name = device.data.name
3839
device.is_on = device.data.features.switchable.status
40+
device.brightness = device.data.features.dimmable.status
41+
type(device).is_dimmable = PropertyMock(
42+
side_effect=lambda s=state: s["is_dimmable"]
43+
)
3944
device_list.append(device)
4045
return device_list
4146

@@ -47,6 +52,7 @@ def build_devices_mock(devices: Devices):
4752
type(devices).devices = PropertyMock(
4853
side_effect=lambda d=devices: build_devices_mock(d)
4954
)
55+
devices.set_is_dimmable = lambda value, s=state: s.update(is_dimmable=value)
5056
yield devices
5157

5258

tests/components/lunatone/test_light.py

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
from syrupy.assertion import SnapshotAssertion
66

7-
from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN
7+
from homeassistant.components.light import ATTR_BRIGHTNESS, DOMAIN as LIGHT_DOMAIN
88
from homeassistant.const import (
99
ATTR_ENTITY_ID,
1010
SERVICE_TURN_OFF,
@@ -47,7 +47,6 @@ async def test_turn_on_off(
4747
mock_lunatone_devices: AsyncMock,
4848
mock_lunatone_info: AsyncMock,
4949
mock_config_entry: MockConfigEntry,
50-
entity_registry: er.EntityRegistry,
5150
) -> None:
5251
"""Test the light can be turned on and off."""
5352
await setup_integration(hass, mock_config_entry)
@@ -77,3 +76,59 @@ async def fake_update():
7776

7877
state = hass.states.get(TEST_ENTITY_ID)
7978
assert state.state == STATE_OFF
79+
80+
81+
async def test_turn_on_off_with_brightness(
82+
hass: HomeAssistant,
83+
mock_lunatone_devices: AsyncMock,
84+
mock_lunatone_info: AsyncMock,
85+
mock_config_entry: MockConfigEntry,
86+
) -> None:
87+
"""Test the light can be turned on with brightness."""
88+
expected_brightness = 128
89+
brightness_percentages = iter([50.0, 0.0, 50.0])
90+
91+
mock_lunatone_devices.set_is_dimmable(True)
92+
93+
await setup_integration(hass, mock_config_entry)
94+
95+
async def fake_update():
96+
brightness = next(brightness_percentages)
97+
device = mock_lunatone_devices.data.devices[0]
98+
device.features.switchable.status = brightness > 0
99+
device.features.dimmable.status = brightness
100+
101+
mock_lunatone_devices.async_update.side_effect = fake_update
102+
103+
await hass.services.async_call(
104+
LIGHT_DOMAIN,
105+
SERVICE_TURN_ON,
106+
{ATTR_ENTITY_ID: TEST_ENTITY_ID, ATTR_BRIGHTNESS: expected_brightness},
107+
blocking=True,
108+
)
109+
110+
state = hass.states.get(TEST_ENTITY_ID)
111+
assert state.state == STATE_ON
112+
assert state.attributes["brightness"] == expected_brightness
113+
114+
await hass.services.async_call(
115+
LIGHT_DOMAIN,
116+
SERVICE_TURN_OFF,
117+
{ATTR_ENTITY_ID: TEST_ENTITY_ID},
118+
blocking=True,
119+
)
120+
121+
state = hass.states.get(TEST_ENTITY_ID)
122+
assert state.state == STATE_OFF
123+
assert not state.attributes["brightness"]
124+
125+
await hass.services.async_call(
126+
LIGHT_DOMAIN,
127+
SERVICE_TURN_ON,
128+
{ATTR_ENTITY_ID: TEST_ENTITY_ID},
129+
blocking=True,
130+
)
131+
132+
state = hass.states.get(TEST_ENTITY_ID)
133+
assert state.state == STATE_ON
134+
assert state.attributes["brightness"] == expected_brightness

0 commit comments

Comments
 (0)