2020-07-19 20:48:08 +00:00
|
|
|
"""Platform for Control4 Lights."""
|
2021-07-19 12:14:09 +00:00
|
|
|
from __future__ import annotations
|
|
|
|
|
2020-07-19 20:48:08 +00:00
|
|
|
import asyncio
|
|
|
|
from datetime import timedelta
|
|
|
|
import logging
|
2022-07-31 18:46:13 +00:00
|
|
|
from typing import Any
|
2020-07-19 20:48:08 +00:00
|
|
|
|
|
|
|
from pyControl4.error_handling import C4Exception
|
|
|
|
from pyControl4.light import C4Light
|
|
|
|
|
|
|
|
from homeassistant.components.light import (
|
|
|
|
ATTR_BRIGHTNESS,
|
|
|
|
ATTR_TRANSITION,
|
2022-04-23 05:49:08 +00:00
|
|
|
ColorMode,
|
2020-07-19 20:48:08 +00:00
|
|
|
LightEntity,
|
2022-04-05 21:49:20 +00:00
|
|
|
LightEntityFeature,
|
2020-07-19 20:48:08 +00:00
|
|
|
)
|
|
|
|
from homeassistant.config_entries import ConfigEntry
|
|
|
|
from homeassistant.const import CONF_SCAN_INTERVAL
|
|
|
|
from homeassistant.core import HomeAssistant
|
2022-01-07 15:24:13 +00:00
|
|
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
2020-07-19 20:48:08 +00:00
|
|
|
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
|
|
|
|
|
|
|
from . import Control4Entity, get_items_of_category
|
2020-08-04 19:35:28 +00:00
|
|
|
from .const import CONF_DIRECTOR, CONTROL4_ENTITY_TYPE, DOMAIN
|
2023-03-30 08:33:01 +00:00
|
|
|
from .director_utils import update_variables_for_config_entry
|
2020-07-19 20:48:08 +00:00
|
|
|
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
CONTROL4_CATEGORY = "lights"
|
|
|
|
CONTROL4_NON_DIMMER_VAR = "LIGHT_STATE"
|
|
|
|
CONTROL4_DIMMER_VAR = "LIGHT_LEVEL"
|
|
|
|
|
|
|
|
|
|
|
|
async def async_setup_entry(
|
2022-01-07 15:24:13 +00:00
|
|
|
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
|
|
|
) -> None:
|
2020-07-19 20:48:08 +00:00
|
|
|
"""Set up Control4 lights from a config entry."""
|
|
|
|
entry_data = hass.data[DOMAIN][entry.entry_id]
|
|
|
|
scan_interval = entry_data[CONF_SCAN_INTERVAL]
|
|
|
|
_LOGGER.debug(
|
2020-08-27 11:56:20 +00:00
|
|
|
"Scan interval = %s",
|
|
|
|
scan_interval,
|
2020-07-19 20:48:08 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
async def async_update_data_non_dimmer():
|
|
|
|
"""Fetch data from Control4 director for non-dimmer lights."""
|
|
|
|
try:
|
2023-03-30 08:33:01 +00:00
|
|
|
return await update_variables_for_config_entry(
|
|
|
|
hass, entry, {CONTROL4_NON_DIMMER_VAR}
|
|
|
|
)
|
2020-07-19 20:48:08 +00:00
|
|
|
except C4Exception as err:
|
2020-08-28 11:50:32 +00:00
|
|
|
raise UpdateFailed(f"Error communicating with API: {err}") from err
|
2020-07-19 20:48:08 +00:00
|
|
|
|
|
|
|
async def async_update_data_dimmer():
|
|
|
|
"""Fetch data from Control4 director for dimmer lights."""
|
|
|
|
try:
|
2023-03-30 08:33:01 +00:00
|
|
|
return await update_variables_for_config_entry(
|
|
|
|
hass, entry, {CONTROL4_DIMMER_VAR}
|
|
|
|
)
|
2020-07-19 20:48:08 +00:00
|
|
|
except C4Exception as err:
|
2020-08-28 11:50:32 +00:00
|
|
|
raise UpdateFailed(f"Error communicating with API: {err}") from err
|
2020-07-19 20:48:08 +00:00
|
|
|
|
|
|
|
non_dimmer_coordinator = DataUpdateCoordinator(
|
|
|
|
hass,
|
|
|
|
_LOGGER,
|
|
|
|
name="light",
|
|
|
|
update_method=async_update_data_non_dimmer,
|
|
|
|
update_interval=timedelta(seconds=scan_interval),
|
|
|
|
)
|
|
|
|
dimmer_coordinator = DataUpdateCoordinator(
|
|
|
|
hass,
|
|
|
|
_LOGGER,
|
|
|
|
name="light",
|
|
|
|
update_method=async_update_data_dimmer,
|
|
|
|
update_interval=timedelta(seconds=scan_interval),
|
|
|
|
)
|
|
|
|
|
|
|
|
# Fetch initial data so we have data when entities subscribe
|
|
|
|
await non_dimmer_coordinator.async_refresh()
|
|
|
|
await dimmer_coordinator.async_refresh()
|
|
|
|
|
|
|
|
items_of_category = await get_items_of_category(hass, entry, CONTROL4_CATEGORY)
|
2020-08-19 18:05:02 +00:00
|
|
|
|
|
|
|
entity_list = []
|
2020-07-19 20:48:08 +00:00
|
|
|
for item in items_of_category:
|
2020-08-19 18:05:02 +00:00
|
|
|
try:
|
|
|
|
if item["type"] == CONTROL4_ENTITY_TYPE:
|
|
|
|
item_name = item["name"]
|
|
|
|
item_id = item["id"]
|
|
|
|
item_parent_id = item["parentId"]
|
|
|
|
|
|
|
|
item_manufacturer = None
|
|
|
|
item_device_name = None
|
|
|
|
item_model = None
|
|
|
|
|
|
|
|
for parent_item in items_of_category:
|
|
|
|
if parent_item["id"] == item_parent_id:
|
|
|
|
item_manufacturer = parent_item["manufacturer"]
|
|
|
|
item_device_name = parent_item["name"]
|
|
|
|
item_model = parent_item["model"]
|
2020-07-19 20:48:08 +00:00
|
|
|
else:
|
2020-08-19 18:05:02 +00:00
|
|
|
continue
|
|
|
|
except KeyError:
|
|
|
|
_LOGGER.exception(
|
2020-08-27 11:56:20 +00:00
|
|
|
"Unknown device properties received from Control4: %s",
|
|
|
|
item,
|
2020-08-19 18:05:02 +00:00
|
|
|
)
|
|
|
|
continue
|
|
|
|
|
|
|
|
if item_id in dimmer_coordinator.data:
|
|
|
|
item_is_dimmer = True
|
|
|
|
item_coordinator = dimmer_coordinator
|
|
|
|
elif item_id in non_dimmer_coordinator.data:
|
|
|
|
item_is_dimmer = False
|
|
|
|
item_coordinator = non_dimmer_coordinator
|
|
|
|
else:
|
|
|
|
director = entry_data[CONF_DIRECTOR]
|
|
|
|
item_variables = await director.getItemVariables(item_id)
|
|
|
|
_LOGGER.warning(
|
2022-12-22 09:12:50 +00:00
|
|
|
(
|
|
|
|
"Couldn't get light state data for %s, skipping setup. Available"
|
|
|
|
" variables from Control4: %s"
|
|
|
|
),
|
2020-08-19 18:05:02 +00:00
|
|
|
item_name,
|
|
|
|
item_variables,
|
2020-07-19 20:48:08 +00:00
|
|
|
)
|
2020-08-19 18:05:02 +00:00
|
|
|
continue
|
|
|
|
|
|
|
|
entity_list.append(
|
|
|
|
Control4Light(
|
|
|
|
entry_data,
|
|
|
|
item_coordinator,
|
|
|
|
item_name,
|
|
|
|
item_id,
|
|
|
|
item_device_name,
|
|
|
|
item_manufacturer,
|
|
|
|
item_model,
|
|
|
|
item_parent_id,
|
|
|
|
item_is_dimmer,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
async_add_entities(entity_list, True)
|
2020-07-19 20:48:08 +00:00
|
|
|
|
|
|
|
|
|
|
|
class Control4Light(Control4Entity, LightEntity):
|
|
|
|
"""Control4 light entity."""
|
|
|
|
|
|
|
|
def __init__(
|
|
|
|
self,
|
|
|
|
entry_data: dict,
|
|
|
|
coordinator: DataUpdateCoordinator,
|
|
|
|
name: str,
|
|
|
|
idx: int,
|
2021-07-19 12:14:09 +00:00
|
|
|
device_name: str | None,
|
|
|
|
device_manufacturer: str | None,
|
|
|
|
device_model: str | None,
|
2020-07-19 20:48:08 +00:00
|
|
|
device_id: int,
|
|
|
|
is_dimmer: bool,
|
2021-05-20 15:51:39 +00:00
|
|
|
) -> None:
|
2020-07-19 20:48:08 +00:00
|
|
|
"""Initialize Control4 light entity."""
|
|
|
|
super().__init__(
|
|
|
|
entry_data,
|
|
|
|
coordinator,
|
|
|
|
name,
|
|
|
|
idx,
|
|
|
|
device_name,
|
|
|
|
device_manufacturer,
|
|
|
|
device_model,
|
|
|
|
device_id,
|
|
|
|
)
|
|
|
|
self._is_dimmer = is_dimmer
|
2022-04-04 08:53:06 +00:00
|
|
|
if is_dimmer:
|
2022-04-23 05:49:08 +00:00
|
|
|
self._attr_color_mode = ColorMode.BRIGHTNESS
|
|
|
|
self._attr_supported_color_modes = {ColorMode.BRIGHTNESS}
|
2022-04-04 08:53:06 +00:00
|
|
|
else:
|
2022-04-23 05:49:08 +00:00
|
|
|
self._attr_color_mode = ColorMode.ONOFF
|
|
|
|
self._attr_supported_color_modes = {ColorMode.ONOFF}
|
2020-07-19 20:48:08 +00:00
|
|
|
|
2023-03-24 07:44:35 +00:00
|
|
|
def _create_api_object(self):
|
2020-08-04 19:35:28 +00:00
|
|
|
"""Create a pyControl4 device object.
|
|
|
|
|
|
|
|
This exists so the director token used is always the latest one, without needing to re-init the entire entity.
|
|
|
|
"""
|
|
|
|
return C4Light(self.entry_data[CONF_DIRECTOR], self._idx)
|
2020-07-19 20:48:08 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def is_on(self):
|
|
|
|
"""Return whether this light is on or off."""
|
2023-03-30 08:33:01 +00:00
|
|
|
if self._is_dimmer:
|
|
|
|
return self.coordinator.data[self._idx][CONTROL4_DIMMER_VAR] > 0
|
|
|
|
return self.coordinator.data[self._idx][CONTROL4_NON_DIMMER_VAR] > 0
|
2020-07-19 20:48:08 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def brightness(self):
|
|
|
|
"""Return the brightness of this light between 0..255."""
|
|
|
|
if self._is_dimmer:
|
2023-03-30 08:33:01 +00:00
|
|
|
return round(self.coordinator.data[self._idx][CONTROL4_DIMMER_VAR] * 2.55)
|
2020-07-19 20:48:08 +00:00
|
|
|
return None
|
|
|
|
|
|
|
|
@property
|
2022-11-22 06:14:47 +00:00
|
|
|
def supported_features(self) -> LightEntityFeature:
|
2020-07-19 20:48:08 +00:00
|
|
|
"""Flag supported features."""
|
|
|
|
if self._is_dimmer:
|
2022-04-05 21:49:20 +00:00
|
|
|
return LightEntityFeature.TRANSITION
|
2022-11-22 06:14:47 +00:00
|
|
|
return LightEntityFeature(0)
|
2020-07-19 20:48:08 +00:00
|
|
|
|
2022-07-31 18:46:13 +00:00
|
|
|
async def async_turn_on(self, **kwargs: Any) -> None:
|
2020-07-19 20:48:08 +00:00
|
|
|
"""Turn the entity on."""
|
2023-03-24 07:44:35 +00:00
|
|
|
c4_light = self._create_api_object()
|
2020-07-19 20:48:08 +00:00
|
|
|
if self._is_dimmer:
|
|
|
|
if ATTR_TRANSITION in kwargs:
|
|
|
|
transition_length = kwargs[ATTR_TRANSITION] * 1000
|
|
|
|
else:
|
|
|
|
transition_length = 0
|
|
|
|
if ATTR_BRIGHTNESS in kwargs:
|
|
|
|
brightness = (kwargs[ATTR_BRIGHTNESS] / 255) * 100
|
|
|
|
else:
|
|
|
|
brightness = 100
|
2020-08-04 19:35:28 +00:00
|
|
|
await c4_light.rampToLevel(brightness, transition_length)
|
2020-07-19 20:48:08 +00:00
|
|
|
else:
|
|
|
|
transition_length = 0
|
2020-08-04 19:35:28 +00:00
|
|
|
await c4_light.setLevel(100)
|
2020-07-19 20:48:08 +00:00
|
|
|
if transition_length == 0:
|
|
|
|
transition_length = 1000
|
|
|
|
delay_time = (transition_length / 1000) + 0.7
|
|
|
|
_LOGGER.debug("Delaying light update by %s seconds", delay_time)
|
|
|
|
await asyncio.sleep(delay_time)
|
2020-08-30 18:27:46 +00:00
|
|
|
await self.coordinator.async_request_refresh()
|
2020-07-19 20:48:08 +00:00
|
|
|
|
2022-07-31 18:46:13 +00:00
|
|
|
async def async_turn_off(self, **kwargs: Any) -> None:
|
2020-07-19 20:48:08 +00:00
|
|
|
"""Turn the entity off."""
|
2023-03-24 07:44:35 +00:00
|
|
|
c4_light = self._create_api_object()
|
2020-07-19 20:48:08 +00:00
|
|
|
if self._is_dimmer:
|
|
|
|
if ATTR_TRANSITION in kwargs:
|
|
|
|
transition_length = kwargs[ATTR_TRANSITION] * 1000
|
|
|
|
else:
|
|
|
|
transition_length = 0
|
2020-08-04 19:35:28 +00:00
|
|
|
await c4_light.rampToLevel(0, transition_length)
|
2020-07-19 20:48:08 +00:00
|
|
|
else:
|
|
|
|
transition_length = 0
|
2020-08-04 19:35:28 +00:00
|
|
|
await c4_light.setLevel(0)
|
2020-07-19 20:48:08 +00:00
|
|
|
if transition_length == 0:
|
|
|
|
transition_length = 1500
|
|
|
|
delay_time = (transition_length / 1000) + 0.7
|
|
|
|
_LOGGER.debug("Delaying light update by %s seconds", delay_time)
|
|
|
|
await asyncio.sleep(delay_time)
|
2020-08-30 18:27:46 +00:00
|
|
|
await self.coordinator.async_request_refresh()
|