From d0da457a049c0203019676219ffb87fa35bbcda1 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 22 Jan 2024 15:52:59 +0100 Subject: [PATCH] Add device to Lutron (#107467) * Add typing to Lutron platforms * Add devices to Lutron * Add devices to Lutron * Fix typing * Fix * Add name * Fix lights * Comment out ESA * Fix domain * Fix domain * Fix * Make generic keypad base class --- homeassistant/components/lutron/__init__.py | 14 ++++- .../components/lutron/binary_sensor.py | 8 --- homeassistant/components/lutron/cover.py | 1 + homeassistant/components/lutron/entity.py | 56 ++++++++++++++++--- homeassistant/components/lutron/light.py | 1 + homeassistant/components/lutron/scene.py | 23 +++----- homeassistant/components/lutron/switch.py | 27 ++++----- 7 files changed, 80 insertions(+), 50 deletions(-) diff --git a/homeassistant/components/lutron/__init__.py b/homeassistant/components/lutron/__init__.py index d89797eedc7..486b1643f59 100644 --- a/homeassistant/components/lutron/__init__.py +++ b/homeassistant/components/lutron/__init__.py @@ -17,6 +17,7 @@ from homeassistant.const import ( from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, HomeAssistant from homeassistant.data_entry_flow import FlowResultType import homeassistant.helpers.config_validation as cv +import homeassistant.helpers.device_registry as dr from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue from homeassistant.helpers.typing import ConfigType from homeassistant.util import slugify @@ -167,7 +168,7 @@ class LutronData: buttons: list[LutronButton] covers: list[tuple[str, Output]] lights: list[tuple[str, Output]] - scenes: list[tuple[str, str, Button, Led]] + scenes: list[tuple[str, Keypad, Button, Led]] switches: list[tuple[str, Output]] @@ -218,11 +219,20 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b (led for led in keypad.leds if led.number == button.number), None, ) - entry_data.scenes.append((area.name, keypad.name, button, led)) + entry_data.scenes.append((area.name, keypad, button, led)) entry_data.buttons.append(LutronButton(hass, area.name, keypad, button)) if area.occupancy_group is not None: entry_data.binary_sensors.append((area.name, area.occupancy_group)) + + device_registry = dr.async_get(hass) + device_registry.async_get_or_create( + config_entry_id=config_entry.entry_id, + identifiers={(DOMAIN, lutron_client.guid)}, + manufacturer="Lutron", + name="Main repeater", + ) + hass.data.setdefault(DOMAIN, {})[config_entry.entry_id] = entry_data await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS) diff --git a/homeassistant/components/lutron/binary_sensor.py b/homeassistant/components/lutron/binary_sensor.py index 8433724d489..3adabfb3c9a 100644 --- a/homeassistant/components/lutron/binary_sensor.py +++ b/homeassistant/components/lutron/binary_sensor.py @@ -58,14 +58,6 @@ class LutronOccupancySensor(LutronDevice, BinarySensorEntity): # Error cases will end up treated as unoccupied. return self._lutron_device.state == OccupancyGroup.State.OCCUPIED - @property - def name(self) -> str: - """Return the name of the device.""" - # The default LutronDevice naming would create 'Kitchen Occ Kitchen', - # but since there can only be one OccupancyGroup per area we go - # with something shorter. - return f"{self._area_name} Occupancy" - @property def extra_state_attributes(self) -> Mapping[str, Any] | None: """Return the state attributes.""" diff --git a/homeassistant/components/lutron/cover.py b/homeassistant/components/lutron/cover.py index 1941c050aa4..9aace54757f 100644 --- a/homeassistant/components/lutron/cover.py +++ b/homeassistant/components/lutron/cover.py @@ -51,6 +51,7 @@ class LutronCover(LutronDevice, CoverEntity): | CoverEntityFeature.SET_POSITION ) _lutron_device: Output + _attr_name = None @property def is_closed(self) -> bool: diff --git a/homeassistant/components/lutron/entity.py b/homeassistant/components/lutron/entity.py index 423186eceae..4e6d0066a47 100644 --- a/homeassistant/components/lutron/entity.py +++ b/homeassistant/components/lutron/entity.py @@ -1,14 +1,19 @@ """Base class for Lutron devices.""" -from pylutron import Lutron, LutronEntity, LutronEvent +from pylutron import Keypad, Lutron, LutronEntity, LutronEvent +from homeassistant.const import ATTR_IDENTIFIERS, ATTR_VIA_DEVICE +from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.entity import Entity +from .const import DOMAIN -class LutronDevice(Entity): - """Representation of a Lutron device entity.""" + +class LutronBaseEntity(Entity): + """Base class for Lutron entities.""" _attr_should_poll = False + _attr_has_entity_name = True def __init__( self, area_name: str, lutron_device: LutronEntity, controller: Lutron @@ -28,11 +33,6 @@ class LutronDevice(Entity): """Run when invoked by pylutron when the device state changes.""" self.schedule_update_ha_state() - @property - def name(self) -> str: - """Return the name of the device.""" - return f"{self._area_name} {self._lutron_device.name}" - @property def unique_id(self) -> str | None: """Return a unique ID.""" @@ -40,3 +40,43 @@ class LutronDevice(Entity): if self._lutron_device.uuid is None: return None return f"{self._controller.guid}_{self._lutron_device.uuid}" + + +class LutronDevice(LutronBaseEntity): + """Representation of a Lutron device entity.""" + + def __init__( + self, area_name: str, lutron_device: LutronEntity, controller: Lutron + ) -> None: + """Initialize the device.""" + super().__init__(area_name, lutron_device, controller) + self._attr_device_info = DeviceInfo( + identifiers={(DOMAIN, lutron_device.uuid)}, + manufacturer="Lutron", + name=lutron_device.name, + suggested_area=area_name, + via_device=(DOMAIN, controller.guid), + ) + + +class LutronKeypad(LutronBaseEntity): + """Representation of a Lutron Keypad.""" + + def __init__( + self, + area_name: str, + lutron_device: LutronEntity, + controller: Lutron, + keypad: Keypad, + ) -> None: + """Initialize the device.""" + super().__init__(area_name, lutron_device, controller) + self._attr_device_info = DeviceInfo( + identifiers={(DOMAIN, keypad.id)}, + manufacturer="Lutron", + name=keypad.name, + ) + if keypad.type == "MAIN_REPEATER": + self._attr_device_info[ATTR_IDENTIFIERS].add((DOMAIN, controller.guid)) + else: + self._attr_device_info[ATTR_VIA_DEVICE] = (DOMAIN, controller.guid) diff --git a/homeassistant/components/lutron/light.py b/homeassistant/components/lutron/light.py index b6860a4e818..fa7a45734fe 100644 --- a/homeassistant/components/lutron/light.py +++ b/homeassistant/components/lutron/light.py @@ -52,6 +52,7 @@ class LutronLight(LutronDevice, LightEntity): _attr_supported_color_modes = {ColorMode.BRIGHTNESS} _lutron_device: Output _prev_brightness: int | None = None + _attr_name = None @property def brightness(self) -> int: diff --git a/homeassistant/components/lutron/scene.py b/homeassistant/components/lutron/scene.py index ae8f787d290..a4a505c8477 100644 --- a/homeassistant/components/lutron/scene.py +++ b/homeassistant/components/lutron/scene.py @@ -3,7 +3,7 @@ from __future__ import annotations from typing import Any -from pylutron import Button, Led, Lutron +from pylutron import Button, Keypad, Lutron from homeassistant.components.scene import Scene from homeassistant.config_entries import ConfigEntry @@ -11,7 +11,7 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback from . import DOMAIN, LutronData -from .entity import LutronDevice +from .entity import LutronKeypad async def async_setup_entry( @@ -28,14 +28,14 @@ async def async_setup_entry( async_add_entities( [ - LutronScene(area_name, keypad_name, device, led, entry_data.client) - for area_name, keypad_name, device, led in entry_data.scenes + LutronScene(area_name, keypad, device, entry_data.client) + for area_name, keypad, device, led in entry_data.scenes ], True, ) -class LutronScene(LutronDevice, Scene): +class LutronScene(LutronKeypad, Scene): """Representation of a Lutron Scene.""" _lutron_device: Button @@ -43,21 +43,14 @@ class LutronScene(LutronDevice, Scene): def __init__( self, area_name: str, - keypad_name: str, + keypad: Keypad, lutron_device: Button, - lutron_led: Led, controller: Lutron, ) -> None: """Initialize the scene/button.""" - super().__init__(area_name, lutron_device, controller) - self._keypad_name = keypad_name - self._led = lutron_led + super().__init__(area_name, lutron_device, controller, keypad) + self._attr_name = lutron_device.name def activate(self, **kwargs: Any) -> None: """Activate the scene.""" self._lutron_device.press() - - @property - def name(self) -> str: - """Return the name of the device.""" - return f"{self._area_name} {self._keypad_name}: {self._lutron_device.name}" diff --git a/homeassistant/components/lutron/switch.py b/homeassistant/components/lutron/switch.py index 5cb7dcf53d8..14331fa500d 100644 --- a/homeassistant/components/lutron/switch.py +++ b/homeassistant/components/lutron/switch.py @@ -4,7 +4,7 @@ from __future__ import annotations from collections.abc import Mapping from typing import Any -from pylutron import Button, Led, Lutron, Output +from pylutron import Button, Keypad, Led, Lutron, Output from homeassistant.components.switch import SwitchEntity from homeassistant.config_entries import ConfigEntry @@ -12,7 +12,7 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback from . import DOMAIN, LutronData -from .entity import LutronDevice +from .entity import LutronDevice, LutronKeypad async def async_setup_entry( @@ -33,11 +33,9 @@ async def async_setup_entry( entities.append(LutronSwitch(area_name, device, entry_data.client)) # Add the indicator LEDs for scenes (keypad buttons) - for area_name, keypad_name, scene, led in entry_data.scenes: + for area_name, keypad, scene, led in entry_data.scenes: if led is not None: - entities.append( - LutronLed(area_name, keypad_name, scene, led, entry_data.client) - ) + entities.append(LutronLed(area_name, keypad, scene, led, entry_data.client)) async_add_entities(entities, True) @@ -77,7 +75,7 @@ class LutronSwitch(LutronDevice, SwitchEntity): self._prev_state = self._lutron_device.level > 0 -class LutronLed(LutronDevice, SwitchEntity): +class LutronLed(LutronKeypad, SwitchEntity): """Representation of a Lutron Keypad LED.""" _lutron_device: Led @@ -85,15 +83,15 @@ class LutronLed(LutronDevice, SwitchEntity): def __init__( self, area_name: str, - keypad_name: str, + keypad: Keypad, scene_device: Button, led_device: Led, controller: Lutron, ) -> None: """Initialize the switch.""" - self._keypad_name = keypad_name - self._scene_name = scene_device.name - super().__init__(area_name, led_device, controller) + super().__init__(area_name, led_device, controller, keypad) + self._keypad_name = keypad.name + self._attr_name = scene_device.name def turn_on(self, **kwargs: Any) -> None: """Turn the LED on.""" @@ -108,7 +106,7 @@ class LutronLed(LutronDevice, SwitchEntity): """Return the state attributes.""" return { "keypad": self._keypad_name, - "scene": self._scene_name, + "scene": self._attr_name, "led": self._lutron_device.name, } @@ -117,11 +115,6 @@ class LutronLed(LutronDevice, SwitchEntity): """Return true if device is on.""" return self._lutron_device.last_state - @property - def name(self) -> str: - """Return the name of the LED.""" - return f"{self._area_name} {self._keypad_name}: {self._scene_name} LED" - def update(self) -> None: """Call when forcing a refresh of the device.""" # The following property getter actually triggers an update in Lutron