mirror of
https://github.com/home-assistant/core.git
synced 2026-06-04 22:53:54 +01:00
229 lines
7.1 KiB
Python
229 lines
7.1 KiB
Python
"""Light platform for Avea."""
|
|
|
|
from contextlib import suppress
|
|
import logging
|
|
from typing import Any
|
|
|
|
import avea
|
|
from bleak.exc import BleakError
|
|
|
|
from homeassistant.components.light import (
|
|
ATTR_BRIGHTNESS,
|
|
ATTR_HS_COLOR,
|
|
ColorMode,
|
|
LightEntity,
|
|
)
|
|
from homeassistant.config_entries import SOURCE_IMPORT
|
|
from homeassistant.const import CONF_ADDRESS, CONF_NAME
|
|
from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, HomeAssistant
|
|
from homeassistant.data_entry_flow import FlowResultType
|
|
from homeassistant.exceptions import PlatformNotReady
|
|
from homeassistant.helpers import issue_registry as ir
|
|
from homeassistant.helpers.entity_platform import (
|
|
AddConfigEntryEntitiesCallback,
|
|
AddEntitiesCallback,
|
|
)
|
|
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
|
from homeassistant.util import color as color_util
|
|
|
|
from . import AveaConfigEntry
|
|
from .const import DOMAIN, INTEGRATION_TITLE, UNKNOWN_NAME
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
UPDATE_EXCEPTIONS = (BleakError, OSError, RuntimeError)
|
|
BREAKS_IN_HA_VERSION = "2026.12.0"
|
|
AVEA_MAX_BRIGHTNESS = 4095
|
|
|
|
|
|
def _normalize_name(name: str | None) -> str | None:
|
|
"""Return a valid Avea name."""
|
|
if not name or name == UNKNOWN_NAME:
|
|
return None
|
|
return name
|
|
|
|
|
|
def _ha_brightness_to_avea(brightness: int) -> int:
|
|
"""Convert Home Assistant brightness to Avea brightness."""
|
|
return round((brightness / 255) * AVEA_MAX_BRIGHTNESS)
|
|
|
|
|
|
def _avea_brightness_to_ha(brightness: int) -> int:
|
|
"""Convert Avea brightness to Home Assistant brightness."""
|
|
return round(255 * (brightness / AVEA_MAX_BRIGHTNESS))
|
|
|
|
|
|
def _create_deprecated_yaml_issue(hass: HomeAssistant) -> None:
|
|
"""Create the deprecated YAML issue for Avea."""
|
|
ir.async_create_issue(
|
|
hass,
|
|
HOMEASSISTANT_DOMAIN,
|
|
f"deprecated_yaml_{DOMAIN}",
|
|
breaks_in_ha_version=BREAKS_IN_HA_VERSION,
|
|
is_fixable=False,
|
|
is_persistent=False,
|
|
issue_domain=DOMAIN,
|
|
severity=ir.IssueSeverity.WARNING,
|
|
translation_key="deprecated_yaml",
|
|
translation_placeholders={
|
|
"domain": DOMAIN,
|
|
"integration_title": INTEGRATION_TITLE,
|
|
},
|
|
)
|
|
|
|
|
|
def _create_yaml_import_failed_issue(hass: HomeAssistant) -> None:
|
|
"""Create a repair issue when the Avea YAML import cannot find bulbs."""
|
|
ir.async_create_issue(
|
|
hass,
|
|
DOMAIN,
|
|
"deprecated_yaml_import_issue_no_bulbs",
|
|
breaks_in_ha_version=BREAKS_IN_HA_VERSION,
|
|
is_fixable=False,
|
|
issue_domain=DOMAIN,
|
|
severity=ir.IssueSeverity.WARNING,
|
|
translation_key="deprecated_yaml_import_issue_no_bulbs",
|
|
translation_placeholders={
|
|
"domain": DOMAIN,
|
|
"integration_title": INTEGRATION_TITLE,
|
|
},
|
|
)
|
|
|
|
|
|
async def async_setup_entry(
|
|
hass: HomeAssistant,
|
|
entry: AveaConfigEntry,
|
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
|
) -> None:
|
|
"""Set up the Avea light platform."""
|
|
async_add_entities(
|
|
[AveaLight(entry.runtime_data, entry.title)], update_before_add=True
|
|
)
|
|
|
|
|
|
def _discover_bulbs_for_import() -> list[dict[str, str]]:
|
|
"""Discover and validate Avea bulbs for YAML import."""
|
|
discovered_bulbs: list[dict[str, str]] = []
|
|
|
|
for bulb in avea.discover_avea_bulbs():
|
|
address = bulb.addr
|
|
try:
|
|
name = bulb.get_name()
|
|
brightness = bulb.get_brightness()
|
|
except UPDATE_EXCEPTIONS as err:
|
|
_LOGGER.warning(
|
|
"Skipping Avea bulb %s during YAML import due to read failure: %s",
|
|
address,
|
|
err,
|
|
)
|
|
continue
|
|
finally:
|
|
with suppress(*UPDATE_EXCEPTIONS):
|
|
bulb.close()
|
|
|
|
if brightness is None:
|
|
_LOGGER.warning(
|
|
"Skipping Avea bulb %s during YAML import due to"
|
|
" read failure: brightness is None",
|
|
address,
|
|
)
|
|
continue
|
|
|
|
discovered_bulbs.append(
|
|
{
|
|
CONF_ADDRESS: address,
|
|
CONF_NAME: _normalize_name(name)
|
|
or _normalize_name(bulb.name)
|
|
or address,
|
|
}
|
|
)
|
|
|
|
return discovered_bulbs
|
|
|
|
|
|
async def async_setup_platform(
|
|
hass: HomeAssistant,
|
|
config: ConfigType,
|
|
async_add_entities: AddEntitiesCallback,
|
|
discovery_info: DiscoveryInfoType | None = None,
|
|
) -> None:
|
|
"""Import the Avea YAML platform into config entries."""
|
|
try:
|
|
bulbs = await hass.async_add_executor_job(_discover_bulbs_for_import)
|
|
except UPDATE_EXCEPTIONS as err:
|
|
raise PlatformNotReady("Could not discover Avea bulbs for YAML import") from err
|
|
|
|
if not bulbs:
|
|
_create_yaml_import_failed_issue(hass)
|
|
|
|
for bulb in bulbs:
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN,
|
|
context={"source": SOURCE_IMPORT},
|
|
data=bulb,
|
|
)
|
|
|
|
if (
|
|
result.get("type") is FlowResultType.ABORT
|
|
and result.get("reason") != "already_configured"
|
|
):
|
|
_LOGGER.warning(
|
|
"Skipping Avea YAML import for bulb %s: %s",
|
|
bulb[CONF_ADDRESS],
|
|
result.get("reason"),
|
|
)
|
|
continue
|
|
|
|
_create_deprecated_yaml_issue(hass)
|
|
|
|
|
|
class AveaLight(LightEntity):
|
|
"""Representation of an Avea."""
|
|
|
|
_attr_color_mode = ColorMode.HS
|
|
_attr_supported_color_modes = {ColorMode.HS}
|
|
|
|
def __init__(self, light: avea.Bulb, entry_title: str) -> None:
|
|
"""Initialize an AveaLight."""
|
|
self._light = light
|
|
self._attr_name = entry_title
|
|
self._attr_brightness = light.brightness
|
|
self._last_brightness = 255
|
|
|
|
def turn_on(self, **kwargs: Any) -> None:
|
|
"""Instruct the light to turn on."""
|
|
if not kwargs:
|
|
self._light.set_brightness(_ha_brightness_to_avea(self._last_brightness))
|
|
else:
|
|
if ATTR_BRIGHTNESS in kwargs:
|
|
brightness = kwargs[ATTR_BRIGHTNESS]
|
|
if brightness:
|
|
self._last_brightness = brightness
|
|
self._light.set_brightness(_ha_brightness_to_avea(brightness))
|
|
if ATTR_HS_COLOR in kwargs:
|
|
rgb = color_util.color_hs_to_RGB(*kwargs[ATTR_HS_COLOR])
|
|
self._light.set_rgb(rgb[0], rgb[1], rgb[2])
|
|
|
|
def turn_off(self, **kwargs: Any) -> None:
|
|
"""Instruct the light to turn off."""
|
|
self._light.set_brightness(0)
|
|
self._attr_is_on = False
|
|
self._attr_brightness = 0
|
|
|
|
def update(self) -> None:
|
|
"""Fetch new state data for this light."""
|
|
connected = self._light.connect()
|
|
|
|
try:
|
|
brightness = self._light.get_brightness()
|
|
rgb_color = self._light.get_rgb()
|
|
finally:
|
|
if connected:
|
|
self._light.disconnect()
|
|
|
|
if brightness is not None:
|
|
self._attr_is_on = brightness != 0
|
|
self._attr_brightness = _avea_brightness_to_ha(brightness)
|
|
if self._attr_brightness:
|
|
self._last_brightness = self._attr_brightness
|
|
self._attr_hs_color = color_util.color_RGB_to_hs(*rgb_color)
|