Add event platform to Lutron (#109121)

* Add event platform to Lutron

* Add event platform to Lutron

* Fix

* Fix

* Fix

* Add deprecation note

* Fix

* Fix

* Update homeassistant/components/lutron/event.py

* Update homeassistant/components/lutron/event.py

* Fix
This commit is contained in:
Joost Lekkerkerker 2024-04-23 11:54:19 +02:00 committed by GitHub
parent 85203aeb28
commit 2977ec4872
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 131 additions and 68 deletions

View File

@ -741,6 +741,7 @@ omit =
homeassistant/components/lutron/binary_sensor.py
homeassistant/components/lutron/cover.py
homeassistant/components/lutron/entity.py
homeassistant/components/lutron/event.py
homeassistant/components/lutron/fan.py
homeassistant/components/lutron/light.py
homeassistant/components/lutron/switch.py

View File

@ -3,31 +3,25 @@
from dataclasses import dataclass
import logging
from pylutron import Button, Keypad, Led, Lutron, LutronEvent, OccupancyGroup, Output
from pylutron import Button, Keypad, Led, Lutron, OccupancyGroup, Output
import voluptuous as vol
from homeassistant import config_entries
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
ATTR_ID,
CONF_HOST,
CONF_PASSWORD,
CONF_USERNAME,
Platform,
)
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME, Platform
from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, HomeAssistant
from homeassistant.data_entry_flow import FlowResultType
from homeassistant.helpers import device_registry as dr, entity_registry as er
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
from homeassistant.helpers.typing import ConfigType
from homeassistant.util import slugify
from .const import DOMAIN
PLATFORMS = [
Platform.BINARY_SENSOR,
Platform.COVER,
Platform.EVENT,
Platform.FAN,
Platform.LIGHT,
Platform.SCENE,
@ -105,69 +99,13 @@ async def async_setup(hass: HomeAssistant, base_config: ConfigType) -> bool:
return True
class LutronButton:
"""Representation of a button on a Lutron keypad.
This is responsible for firing events as keypad buttons are pressed
(and possibly released, depending on the button type). It is not
represented as an entity; it simply fires events.
"""
def __init__(
self, hass: HomeAssistant, area_name: str, keypad: Keypad, button: Button
) -> None:
"""Register callback for activity on the button."""
name = f"{keypad.name}: {button.name}"
if button.name == "Unknown Button":
name += f" {button.number}"
self._hass = hass
self._has_release_event = (
button.button_type is not None and "RaiseLower" in button.button_type
)
self._id = slugify(name)
self._keypad = keypad
self._area_name = area_name
self._button_name = button.name
self._button = button
self._event = "lutron_event"
self._full_id = slugify(f"{area_name} {name}")
self._uuid = button.uuid
button.subscribe(self.button_callback, None)
def button_callback(
self, _button: Button, _context: None, event: LutronEvent, _params: dict
) -> None:
"""Fire an event about a button being pressed or released."""
# Events per button type:
# RaiseLower -> pressed/released
# SingleAction -> single
action = None
if self._has_release_event:
if event == Button.Event.PRESSED:
action = "pressed"
else:
action = "released"
elif event == Button.Event.PRESSED:
action = "single"
if action:
data = {
ATTR_ID: self._id,
ATTR_ACTION: action,
ATTR_FULL_ID: self._full_id,
ATTR_UUID: self._uuid,
}
self._hass.bus.fire(self._event, data)
@dataclass(slots=True, kw_only=True)
class LutronData:
"""Storage class for platform global data."""
client: Lutron
binary_sensors: list[tuple[str, OccupancyGroup]]
buttons: list[LutronButton]
buttons: list[tuple[str, Keypad, Button]]
covers: list[tuple[str, Output]]
fans: list[tuple[str, Output]]
lights: list[tuple[str, Output]]
@ -273,8 +211,8 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
led.legacy_uuid,
entry_data.client.guid,
)
entry_data.buttons.append(LutronButton(hass, area.name, keypad, button))
if button.button_type:
entry_data.buttons.append((area.name, keypad, button))
if area.occupancy_group is not None:
entry_data.binary_sensors.append((area.name, area.occupancy_group))
platform = Platform.BINARY_SENSOR

View File

@ -0,0 +1,109 @@
"""Support for Lutron events."""
from enum import StrEnum
from pylutron import Button, Keypad, Lutron, LutronEvent
from homeassistant.components.event import EventEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ATTR_ID
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.util import slugify
from . import ATTR_ACTION, ATTR_FULL_ID, ATTR_UUID, DOMAIN, LutronData
from .entity import LutronKeypad
class LutronEventType(StrEnum):
"""Lutron event types."""
SINGLE_PRESS = "single_press"
PRESS = "press"
RELEASE = "release"
LEGACY_EVENT_TYPES: dict[LutronEventType, str] = {
LutronEventType.SINGLE_PRESS: "single",
LutronEventType.PRESS: "pressed",
LutronEventType.RELEASE: "released",
}
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the Lutron event platform."""
entry_data: LutronData = hass.data[DOMAIN][config_entry.entry_id]
async_add_entities(
LutronEventEntity(area_name, keypad, button, entry_data.client)
for area_name, keypad, button in entry_data.buttons
)
class LutronEventEntity(LutronKeypad, EventEntity):
"""Representation of a Lutron keypad button."""
_attr_translation_key = "button"
def __init__(
self,
area_name: str,
keypad: Keypad,
button: Button,
controller: Lutron,
) -> None:
"""Initialize the button."""
super().__init__(area_name, button, controller, keypad)
if (name := button.name) == "Unknown Button":
name += f" {button.number}"
self._attr_name = name
self._has_release_event = (
button.button_type is not None and "RaiseLower" in button.button_type
)
if self._has_release_event:
self._attr_event_types = [LutronEventType.PRESS, LutronEventType.RELEASE]
else:
self._attr_event_types = [LutronEventType.SINGLE_PRESS]
self._full_id = slugify(f"{area_name} {name}")
self._id = slugify(name)
async def async_added_to_hass(self) -> None:
"""Register callbacks."""
await super().async_added_to_hass()
self._lutron_device.subscribe(self.handle_event, None)
async def async_will_remove_from_hass(self) -> None:
"""Unregister callbacks."""
await super().async_will_remove_from_hass()
# Temporary solution until https://github.com/thecynic/pylutron/pull/93 gets merged
self._lutron_device._subscribers.remove((self.handle_event, None)) # pylint: disable=protected-access
@callback
def handle_event(
self, button: Button, _context: None, event: LutronEvent, _params: dict
) -> None:
"""Handle received event."""
action: LutronEventType | None = None
if self._has_release_event:
if event == Button.Event.PRESSED:
action = LutronEventType.PRESS
else:
action = LutronEventType.RELEASE
elif event == Button.Event.PRESSED:
action = LutronEventType.SINGLE_PRESS
if action:
data = {
ATTR_ID: self._id,
ATTR_ACTION: LEGACY_EVENT_TYPES[action],
ATTR_FULL_ID: self._full_id,
ATTR_UUID: button.uuid,
}
self.hass.bus.fire("lutron_event", data)
self._trigger_event(action)
self.async_write_ha_state()

View File

@ -22,6 +22,21 @@
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]"
}
},
"entity": {
"event": {
"button": {
"state_attributes": {
"event_type": {
"state": {
"single_press": "Single press",
"press": "Press",
"release": "Release"
}
}
}
}
}
},
"issues": {
"deprecated_yaml_import_issue_cannot_connect": {
"title": "The Lutron YAML configuration import cannot connect to server",