Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add lutron fan entity #107402

Merged
merged 20 commits into from
Jan 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -718,6 +718,7 @@ omit =
homeassistant/components/lutron/binary_sensor.py
homeassistant/components/lutron/cover.py
homeassistant/components/lutron/entity.py
homeassistant/components/lutron/fan.py
homeassistant/components/lutron/light.py
homeassistant/components/lutron/switch.py
homeassistant/components/lutron_caseta/__init__.py
Expand Down
7 changes: 7 additions & 0 deletions homeassistant/components/lutron/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
PLATFORMS = [
Platform.BINARY_SENSOR,
Platform.COVER,
Platform.FAN,
Platform.LIGHT,
Platform.SCENE,
Platform.SWITCH,
Expand Down Expand Up @@ -167,6 +168,7 @@ class LutronData:
binary_sensors: list[tuple[str, OccupancyGroup]]
buttons: list[LutronButton]
covers: list[tuple[str, Output]]
fans: list[tuple[str, Output]]
lights: list[tuple[str, Output]]
scenes: list[tuple[str, Keypad, Button, Led]]
switches: list[tuple[str, Output]]
Expand All @@ -189,6 +191,7 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
binary_sensors=[],
buttons=[],
covers=[],
fans=[],
lights=[],
scenes=[],
switches=[],
Expand All @@ -201,6 +204,10 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
_LOGGER.debug("Working on output %s", output.type)
if output.type == "SYSTEM_SHADE":
entry_data.covers.append((area.name, output))
elif output.type == "CEILING_FAN_TYPE":
entry_data.fans.append((area.name, output))
# Deprecated, should be removed in 2024.8
entry_data.lights.append((area.name, output))
elif output.is_dimmable:
entry_data.lights.append((area.name, output))
else:
Expand Down
89 changes: 89 additions & 0 deletions homeassistant/components/lutron/fan.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
"""Lutron fan platform."""
wilburCforce marked this conversation as resolved.
Show resolved Hide resolved
from __future__ import annotations

import logging
from typing import Any

from pylutron import Output

from homeassistant.components.fan import FanEntity, FanEntityFeature
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

_LOGGER = logging.getLogger(__name__)


async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the Lutron fan platform.

Adds fan controls from the Main Repeater associated with the config_entry as
fan entities.
"""
entry_data: LutronData = hass.data[DOMAIN][config_entry.entry_id]
async_add_entities(
[
LutronFan(area_name, device, entry_data.client)
for area_name, device in entry_data.fans
],
True,
)


class LutronFan(LutronDevice, FanEntity):
"""Representation of a Lutron fan."""

_attr_name = None
_attr_should_poll = False
_attr_speed_count = 3
_attr_supported_features = FanEntityFeature.SET_SPEED
_lutron_device: Output
_prev_percentage: int | None = None

def set_percentage(self, percentage: int) -> None:
"""Set the speed of the fan, as a percentage."""
if percentage > 0:
self._prev_percentage = percentage
self._lutron_device.level = percentage
wilburCforce marked this conversation as resolved.
Show resolved Hide resolved
self.schedule_update_ha_state()

def turn_on(
self,
percentage: int | None = None,
preset_mode: str | None = None,
**kwargs: Any,
) -> None:
"""Turn the fan on."""
new_percentage: int | None = None

if percentage is not None:
new_percentage = percentage
elif not self._prev_percentage:
# Default to medium speed
new_percentage = 67
else:
new_percentage = self._prev_percentage
self.set_percentage(new_percentage)

def turn_off(self, **kwargs: Any) -> None:
"""Turn the fan off."""
self.set_percentage(0)

def _request_state(self) -> None:
"""Request the state from the device."""
self._lutron_device.level # pylint: disable=pointless-statement

def _update_attrs(self) -> None:
"""Update the state attributes."""
level = self._lutron_device.last_level()
self._attr_is_on = level > 0
self._attr_percentage = level
if self._prev_percentage is None or level != 0:
self._prev_percentage = level
85 changes: 81 additions & 4 deletions homeassistant/components/lutron/light.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,30 @@
from __future__ import annotations

from collections.abc import Mapping
import logging
from typing import Any

from pylutron import Output

from homeassistant.components.automation import automations_with_entity
from homeassistant.components.light import ATTR_BRIGHTNESS, ColorMode, LightEntity
from homeassistant.components.script import scripts_with_entity
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.issue_registry import (
IssueSeverity,
async_create_issue,
create_issue,
)

from . import DOMAIN, LutronData
from .entity import LutronDevice

_LOGGER = logging.getLogger(__name__)


async def async_setup_entry(
hass: HomeAssistant,
Expand All @@ -25,12 +37,50 @@ async def async_setup_entry(
Adds dimmers from the Main Repeater associated with the config_entry as
light entities.
"""
ent_reg = er.async_get(hass)
entry_data: LutronData = hass.data[DOMAIN][config_entry.entry_id]
lights = []

for area_name, device in entry_data.lights:
if device.type == "CEILING_FAN_TYPE2":
# If this is a fan, check to see if this entity already exists.
# If not, do not create a new one.
entity_id = ent_reg.async_get_entity_id(
Platform.LIGHT,
DOMAIN,
f"{entry_data.client.guid}_{device.uuid}",
)
if entity_id:
entity_entry = ent_reg.async_get(entity_id)
assert entity_entry
if entity_entry.disabled:
# If the entity exists and is disabled then we want to remove
# the entity so that the user is using the new fan entity instead.
ent_reg.async_remove(entity_id)
else:
lights.append(LutronLight(area_name, device, entry_data.client))
entity_automations = automations_with_entity(hass, entity_id)
entity_scripts = scripts_with_entity(hass, entity_id)
for item in entity_automations + entity_scripts:
async_create_issue(
hass,
DOMAIN,
f"deprecated_light_fan_{entity_id}_{item}",
breaks_in_ha_version="2024.8.0",
is_fixable=True,
is_persistent=True,
severity=IssueSeverity.WARNING,
translation_key="deprecated_light_fan_entity",
translation_placeholders={
"entity": entity_id,
"info": item,
},
)
else:
lights.append(LutronLight(area_name, device, entry_data.client))

async_add_entities(
[
LutronLight(area_name, device, entry_data.client)
for area_name, device in entry_data.lights
],
lights,
True,
)

Expand All @@ -54,8 +104,24 @@ class LutronLight(LutronDevice, LightEntity):
_prev_brightness: int | None = None
_attr_name = None

def __init__(self, area_name, lutron_device, controller) -> None:
"""Initialize the light."""
super().__init__(area_name, lutron_device, controller)
self._is_fan = lutron_device.type == "CEILING_FAN_TYPE"

def turn_on(self, **kwargs: Any) -> None:
"""Turn the light on."""
if self._is_fan:
create_issue(
self.hass,
DOMAIN,
"deprecated_light_fan_on",
breaks_in_ha_version="2024.8.0",
is_fixable=True,
is_persistent=True,
severity=IssueSeverity.WARNING,
translation_key="deprecated_light_fan_on",
)
if ATTR_BRIGHTNESS in kwargs and self._lutron_device.is_dimmable:
brightness = kwargs[ATTR_BRIGHTNESS]
elif self._prev_brightness == 0:
Expand All @@ -67,6 +133,17 @@ def turn_on(self, **kwargs: Any) -> None:

def turn_off(self, **kwargs: Any) -> None:
"""Turn the light off."""
if self._is_fan:
create_issue(
self.hass,
DOMAIN,
"deprecated_light_fan_off",
breaks_in_ha_version="2024.8.0",
is_fixable=True,
is_persistent=True,
severity=IssueSeverity.WARNING,
translation_key="deprecated_light_fan_off",
)
self._lutron_device.level = 0

@property
Expand Down
12 changes: 12 additions & 0 deletions homeassistant/components/lutron/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,18 @@
"deprecated_yaml_import_issue_unknown": {
"title": "The Lutron YAML configuration import request failed due to an unknown error",
"description": "Configuring Lutron using YAML is being removed but there was an unknown error while importing your existing configuration.\nSetup will not proceed.\n\nThe specific error can be found in the logs. The most likely cause is a networking error or the Main Repeater is down or has an invalid configuration.\n\nVerify that your Lutron system is operating correctly and restart Home Assistant to attempt the import again.\n\nAlternatively, you may remove the Lutron configuration from your YAML configuration entirely, restart Home Assistant, and add the Lutron integration manually."
},
"deprecated_light_fan_entity": {
"title": "Detected Lutron fan entity created as a light",
"description": "Fan entities have been added to the Lutron integration.\nWe detected that entity `{entity}` is being used in `{info}`\n\nWe have created a new fan entity and you should migrate `{info}` to use this new entity.\n\nWhen you are done migrating `{info}` and are ready to have the deprecated light entity removed, disable the entity and restart Home Assistant."
},
"deprecated_light_fan_on": {
"title": "The Lutron integration deprecated fan turned on",
"description": "Fan entities have been added to the Lutron integration.\nPreviously fans were created as lights; this behavior is now deprecated.\n\nYour configuration just turned on a fan created as a light. You should migrate your scenes and automations to use the new fan entity.\n\nWhen you are done migrating your automations and are ready to have the deprecated light entity removed, disable the entity and restart Home Assistant.\n\nAn issue will be created each time the incorrect entity is used to remind you to migrate."
},
"deprecated_light_fan_off": {
"title": "The Lutron integration deprecated fan turned off",
"description": "Fan entities have been added to the Lutron integration.\nPreviously fans were created as lights; this behavior is now deprecated.\n\nYour configuration just turned off a fan created as a light. You should migrate your scenes and automations to use the new fan entity.\n\nWhen you are done migrating your automations and are ready to have the deprecated light entity removed, disable the entity and restart Home Assistant.\n\nAn issue will be created each time the incorrect entity is used to remind you to migrate."
}
}
}
Loading