custom_components/govee_bluetooth/light.py
"""Light entity for Govee BLE strips."""
from __future__ import annotations
import logging
from typing import Any
from homeassistant.components.light import (
ATTR_BRIGHTNESS,
ATTR_COLOR_TEMP_KELVIN,
ATTR_EFFECT,
ATTR_RGB_COLOR,
ColorMode,
LightEntity,
LightEntityFeature,
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import device_registry as dr
from homeassistant.helpers.device_registry import CONNECTION_BLUETOOTH, DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from . import GoveeConfigEntry
from .const import DOMAIN, MAX_KELVIN, MIN_KELVIN
from .coordinator import GoveeCoordinator
from .device import GoveeConnectionError
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(
hass: HomeAssistant, entry: GoveeConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
async_add_entities([GoveeBleLight(entry.runtime_data)])
class GoveeBleLight(CoordinatorEntity[GoveeCoordinator], LightEntity):
"""A Govee strip controlled locally over Bluetooth."""
_attr_has_entity_name = True
_attr_name = None
_attr_translation_key = "strip"
_attr_supported_color_modes = {ColorMode.RGB, ColorMode.COLOR_TEMP}
_attr_min_color_temp_kelvin = MIN_KELVIN
_attr_max_color_temp_kelvin = MAX_KELVIN
def __init__(self, coordinator: GoveeCoordinator) -> None:
super().__init__(coordinator)
device = coordinator.device
self._attr_unique_id = device.address.replace(":", "").lower()
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, device.address)},
connections={(CONNECTION_BLUETOOTH, device.address)},
manufacturer="Govee",
model=device.profile.model,
name=device.name,
sw_version=device.state.firmware,
hw_version=device.state.hardware,
)
# The model's measured range; HA's colour-temperature slider and any
# service call get clamped to it rather than to the widest family.
self._attr_min_color_temp_kelvin = device.profile.min_kelvin
self._attr_max_color_temp_kelvin = device.profile.max_kelvin
if device.profile.supports_scenes:
self._attr_supported_features = LightEntityFeature.EFFECT
self._attr_effect_list = list(device.profile.scenes)
@property
def _state(self):
return self.coordinator.device.state
@property
def available(self) -> bool:
return self.coordinator.available
@property
def is_on(self) -> bool | None:
return self._state.is_on
@property
def brightness(self) -> int | None:
return self._state.brightness
@property
def rgb_color(self) -> tuple[int, int, int] | None:
return self._state.rgb_color
@property
def color_temp_kelvin(self) -> int | None:
return self._state.color_temp_kelvin
@property
def color_mode(self) -> ColorMode:
return ColorMode.COLOR_TEMP if self._state.white_mode else ColorMode.RGB
@property
def effect(self) -> str | None:
return self._state.effect
@property
def extra_state_attributes(self) -> dict[str, Any]:
attrs: dict[str, Any] = {
"bluetooth_address": self.coordinator.device.address,
"field_provenance": self._state.provenance,
}
if self._state.firmware:
attrs["firmware"] = self._state.firmware
return attrs
def _handle_coordinator_update(self) -> None:
firmware = self._state.firmware
if firmware and self.device_entry and self.device_entry.sw_version != firmware:
dr.async_get(self.hass).async_update_device(self.device_entry.id, sw_version=firmware)
hardware = self._state.hardware
if hardware and self.device_entry and self.device_entry.hw_version != hardware:
dr.async_get(self.hass).async_update_device(self.device_entry.id, hw_version=hardware)
super()._handle_coordinator_update()
async def async_turn_on(self, **kwargs: Any) -> None:
brightness = kwargs.get(ATTR_BRIGHTNESS)
if brightness == 0:
await self.async_turn_off()
return
try:
await self.coordinator.device.async_turn_on(
brightness=brightness,
rgb_color=kwargs.get(ATTR_RGB_COLOR),
color_temp_kelvin=kwargs.get(ATTR_COLOR_TEMP_KELVIN),
effect=kwargs.get(ATTR_EFFECT),
)
except GoveeConnectionError as err:
raise HomeAssistantError(str(err)) from err
async def async_turn_off(self, **kwargs: Any) -> None:
try:
await self.coordinator.device.async_turn_off()
except GoveeConnectionError as err:
raise HomeAssistantError(str(err)) from err