From e1923bc13b140da1ddce54bae655c9fb3f6c39a1 Mon Sep 17 00:00:00 2001 From: Shay Levy Date: Tue, 6 Dec 2022 19:44:17 +0200 Subject: [PATCH] Add Shelly support for Plus WallDimmer US (#83385) --- homeassistant/components/shelly/light.py | 49 ++++++++++++++++++-- homeassistant/components/shelly/utils.py | 2 +- tests/components/shelly/conftest.py | 2 + tests/components/shelly/test_light.py | 57 ++++++++++++++++++++++++ 4 files changed, 105 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/shelly/light.py b/homeassistant/components/shelly/light.py index 805b8147ba5..fc44774db02 100644 --- a/homeassistant/components/shelly/light.py +++ b/homeassistant/components/shelly/light.py @@ -109,10 +109,15 @@ def async_setup_rpc_entry( unique_id = f"{coordinator.mac}-switch:{id_}" async_remove_shelly_entity(hass, "switch", unique_id) - if not switch_ids: + if switch_ids: + async_add_entities( + RpcShellySwitchAsLight(coordinator, id_) for id_ in switch_ids + ) return - async_add_entities(RpcShellyLight(coordinator, id_) for id_ in switch_ids) + light_key_ids = get_rpc_key_ids(coordinator.device.status, "light") + if light_key_ids: + async_add_entities(RpcShellyLight(coordinator, id_) for id_ in light_key_ids) class BlockShellyLight(ShellyBlockEntity, LightEntity): @@ -367,8 +372,8 @@ class BlockShellyLight(ShellyBlockEntity, LightEntity): super()._update_callback() -class RpcShellyLight(ShellyRpcEntity, LightEntity): - """Entity that controls a light on RPC based Shelly devices.""" +class RpcShellySwitchAsLight(ShellyRpcEntity, LightEntity): + """Entity that controls a relay as light on RPC based Shelly devices.""" _attr_color_mode = ColorMode.ONOFF _attr_supported_color_modes = {ColorMode.ONOFF} @@ -390,3 +395,39 @@ class RpcShellyLight(ShellyRpcEntity, LightEntity): async def async_turn_off(self, **kwargs: Any) -> None: """Turn off light.""" await self.call_rpc("Switch.Set", {"id": self._id, "on": False}) + + +class RpcShellyLight(ShellyRpcEntity, LightEntity): + """Entity that controls a light on RPC based Shelly devices.""" + + _attr_color_mode = ColorMode.BRIGHTNESS + _attr_supported_color_modes = {ColorMode.BRIGHTNESS} + + def __init__(self, coordinator: ShellyRpcCoordinator, id_: int) -> None: + """Initialize light.""" + super().__init__(coordinator, f"light:{id_}") + self._id = id_ + + @property + def is_on(self) -> bool: + """If light is on.""" + return bool(self.coordinator.device.status[self.key]["output"]) + + @property + def brightness(self) -> int: + """Return the brightness of this light between 0..255.""" + brightness_pct = self.coordinator.device.status[self.key]["brightness"] + return round(255 * brightness_pct / 100) + + async def async_turn_on(self, **kwargs: Any) -> None: + """Turn on light.""" + params: dict[str, Any] = {"id": self._id, "on": True} + + if ATTR_BRIGHTNESS in kwargs: + params["brightness"] = int(100 * (kwargs[ATTR_BRIGHTNESS] + 1) / 255) + + await self.call_rpc("Light.Set", params) + + async def async_turn_off(self, **kwargs: Any) -> None: + """Turn off light.""" + await self.call_rpc("Light.Set", {"id": self._id, "on": False}) diff --git a/homeassistant/components/shelly/utils.py b/homeassistant/components/shelly/utils.py index e13395999b1..a8663b56287 100644 --- a/homeassistant/components/shelly/utils.py +++ b/homeassistant/components/shelly/utils.py @@ -298,7 +298,7 @@ def get_rpc_channel_name(device: RpcDevice, key: str) -> str: entity_name = device.config[key].get("name", device_name) if entity_name is None: - if key.startswith(("input:", "switch:")): + if key.startswith(("input:", "light:", "switch:")): return f"{device_name} {key.replace(':', '_')}" return device_name diff --git a/tests/components/shelly/conftest.py b/tests/components/shelly/conftest.py index 80e53ac6796..214ed3b1503 100644 --- a/tests/components/shelly/conftest.py +++ b/tests/components/shelly/conftest.py @@ -124,6 +124,7 @@ MOCK_BLOCKS = [ MOCK_CONFIG = { "input:0": {"id": 0, "type": "button"}, + "light:0": {"name": "test light_0"}, "switch:0": {"name": "test switch_0"}, "cover:0": {"name": "test cover_0"}, "sys": { @@ -169,6 +170,7 @@ MOCK_STATUS_COAP = { MOCK_STATUS_RPC = { "switch:0": {"output": True}, + "light:0": {"output": True, "brightness": 53.0}, "cloud": {"connected": False}, "cover:0": { "state": "stopped", diff --git a/tests/components/shelly/test_light.py b/tests/components/shelly/test_light.py index 5f8d49fa8aa..7b7376548c8 100644 --- a/tests/components/shelly/test_light.py +++ b/tests/components/shelly/test_light.py @@ -383,3 +383,60 @@ async def test_rpc_device_switch_type_lights_mode(hass, mock_rpc_device, monkeyp ) mock_rpc_device.mock_update() assert hass.states.get("light.test_switch_0").state == STATE_OFF + + +async def test_rpc_light(hass, mock_rpc_device, monkeypatch): + """Test RPC light.""" + entity_id = f"{LIGHT_DOMAIN}.test_light_0" + monkeypatch.delitem(mock_rpc_device.status, "switch:0") + await init_integration(hass, 2) + + # Turn on + await hass.services.async_call( + LIGHT_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: entity_id}, + blocking=True, + ) + + mock_rpc_device.call_rpc.assert_called_once_with("Light.Set", {"id": 0, "on": True}) + state = hass.states.get(entity_id) + assert state.state == STATE_ON + assert state.attributes[ATTR_BRIGHTNESS] == 135 + + # Turn off + mock_rpc_device.call_rpc.reset_mock() + mutate_rpc_device_status(monkeypatch, mock_rpc_device, "light:0", "output", False) + await hass.services.async_call( + LIGHT_DOMAIN, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: entity_id}, + blocking=True, + ) + + mock_rpc_device.mock_update() + mock_rpc_device.call_rpc.assert_called_once_with( + "Light.Set", {"id": 0, "on": False} + ) + state = hass.states.get(entity_id) + assert state.state == STATE_OFF + + # Turn on, brightness = 33 + mock_rpc_device.call_rpc.reset_mock() + await hass.services.async_call( + LIGHT_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: entity_id, ATTR_BRIGHTNESS: 33}, + blocking=True, + ) + + mutate_rpc_device_status(monkeypatch, mock_rpc_device, "light:0", "output", True) + mutate_rpc_device_status(monkeypatch, mock_rpc_device, "light:0", "brightness", 13) + mock_rpc_device.mock_update() + + mock_rpc_device.call_rpc.assert_called_once_with( + "Light.Set", {"id": 0, "on": True, "brightness": 13} + ) + state = hass.states.get(entity_id) + assert state.state == STATE_ON + assert state.attributes[ATTR_BRIGHTNESS] == 33