Skip to content

Commit

Permalink
Add device to Lutron (#107467)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
joostlek authored Jan 22, 2024
1 parent 43daf20 commit d0da457
Show file tree
Hide file tree
Showing 7 changed files with 80 additions and 50 deletions.
14 changes: 12 additions & 2 deletions homeassistant/components/lutron/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
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
Expand Down Expand Up @@ -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]]


Expand Down Expand Up @@ -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)
Expand Down
8 changes: 0 additions & 8 deletions homeassistant/components/lutron/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,14 +58,6 @@ def is_on(self) -> bool:
# 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."""
Expand Down
1 change: 1 addition & 0 deletions homeassistant/components/lutron/cover.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ class LutronCover(LutronDevice, CoverEntity):
| CoverEntityFeature.SET_POSITION
)
_lutron_device: Output
_attr_name = None

@property
def is_closed(self) -> bool:
Expand Down
56 changes: 48 additions & 8 deletions homeassistant/components/lutron/entity.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -28,15 +33,50 @@ def _update_callback(
"""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."""
# Temporary fix for https://github.com/thecynic/pylutron/issues/70
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)
1 change: 1 addition & 0 deletions homeassistant/components/lutron/light.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
23 changes: 8 additions & 15 deletions homeassistant/components/lutron/scene.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@

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
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(
Expand All @@ -28,36 +28,29 @@ 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

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}"
27 changes: 10 additions & 17 deletions homeassistant/components/lutron/switch.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@
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
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(
Expand All @@ -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)


Expand Down Expand Up @@ -77,23 +75,23 @@ def update(self) -> None:
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

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."""
Expand All @@ -108,7 +106,7 @@ def extra_state_attributes(self) -> Mapping[str, Any] | None:
"""Return the state attributes."""
return {
"keypad": self._keypad_name,
"scene": self._scene_name,
"scene": self._attr_name,
"led": self._lutron_device.name,
}

Expand All @@ -117,11 +115,6 @@ def is_on(self) -> bool:
"""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
Expand Down

0 comments on commit d0da457

Please sign in to comment.