Skip to content
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 @@ -981,6 +981,7 @@ omit =
homeassistant/components/reolink/camera.py
homeassistant/components/reolink/entity.py
homeassistant/components/reolink/host.py
homeassistant/components/reolink/light.py
homeassistant/components/reolink/number.py
homeassistant/components/reolink/select.py
homeassistant/components/reolink/siren.py
Expand Down
1 change: 1 addition & 0 deletions homeassistant/components/reolink/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
Platform.BINARY_SENSOR,
Platform.BUTTON,
Platform.CAMERA,
Platform.LIGHT,
Platform.NUMBER,
Platform.SELECT,
Platform.SIREN,
Expand Down
157 changes: 157 additions & 0 deletions homeassistant/components/reolink/light.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
"""Component providing support for Reolink light entities."""
from __future__ import annotations

from collections.abc import Callable
from dataclasses import dataclass
from typing import Any

from reolink_aio.api import Host

from homeassistant.components.light import (
ATTR_BRIGHTNESS,
ColorMode,
LightEntity,
LightEntityDescription,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback

from . import ReolinkData
from .const import DOMAIN
from .entity import ReolinkCoordinatorEntity


@dataclass
class ReolinkLightEntityDescriptionMixin:
"""Mixin values for Reolink light entities."""

is_on_fn: Callable[[Host, int], bool]
turn_on_off_fn: Callable[[Host, int, bool], Any]


@dataclass
class ReolinkLightEntityDescription(
LightEntityDescription, ReolinkLightEntityDescriptionMixin
):
"""A class that describes light entities."""

supported_fn: Callable[[Host, int], bool] = lambda api, ch: True
get_brightness_fn: Callable[[Host, int], int] | None = None
set_brightness_fn: Callable[[Host, int, float], Any] | None = None


LIGHT_ENTITIES = (
ReolinkLightEntityDescription(
key="floodlight",
name="Floodlight",
icon="mdi:spotlight-beam",
supported_fn=lambda api, ch: api.supported(ch, "floodLight"),
is_on_fn=lambda api, ch: api.whiteled_state(ch),
turn_on_off_fn=lambda api, ch, value: api.set_whiteled(ch, state=value),
get_brightness_fn=lambda api, ch: api.whiteled_brightness(ch),
set_brightness_fn=lambda api, ch, value: api.set_whiteled(ch, brightness=value),
),
ReolinkLightEntityDescription(
key="ir_lights",
name="Infra red lights in night mode",
icon="mdi:led-off",
supported_fn=lambda api, ch: api.supported(ch, "ir_lights"),
is_on_fn=lambda api, ch: api.ir_enabled(ch),
turn_on_off_fn=lambda api, ch, value: api.set_ir_lights(ch, value),
),
ReolinkLightEntityDescription(
Comment thread
starkillerOG marked this conversation as resolved.
key="status_led",
name="Status LED",
icon="mdi:lightning-bolt-circle",
entity_category=EntityCategory.CONFIG,
supported_fn=lambda api, ch: api.supported(ch, "status_led"),
is_on_fn=lambda api, ch: api.status_led_enabled(ch),
turn_on_off_fn=lambda api, ch, value: api.set_status_led(ch, value),
),
)


async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up a Reolink light entities."""
reolink_data: ReolinkData = hass.data[DOMAIN][config_entry.entry_id]

async_add_entities(
ReolinkLightEntity(reolink_data, channel, entity_description)
for entity_description in LIGHT_ENTITIES
for channel in reolink_data.host.api.channels
if entity_description.supported_fn(reolink_data.host.api, channel)
)


class ReolinkLightEntity(ReolinkCoordinatorEntity, LightEntity):
"""Base light entity class for Reolink IP cameras."""

entity_description: ReolinkLightEntityDescription

def __init__(
self,
reolink_data: ReolinkData,
channel: int,
Comment thread
starkillerOG marked this conversation as resolved.
entity_description: ReolinkLightEntityDescription,
) -> None:
"""Initialize Reolink light entity."""
super().__init__(reolink_data, channel)
self.entity_description = entity_description

self._attr_unique_id = (
f"{self._host.unique_id}_{channel}_{entity_description.key}"
)

if entity_description.set_brightness_fn is None:
self._attr_supported_color_modes = {ColorMode.ONOFF}
self._attr_color_mode = ColorMode.ONOFF
else:
self._attr_supported_color_modes = {ColorMode.BRIGHTNESS}
self._attr_color_mode = ColorMode.BRIGHTNESS

@property
def is_on(self) -> bool:
"""Return true if light is on."""
return self.entity_description.is_on_fn(self._host.api, self._channel)

@property
def brightness(self) -> int | None:
"""Return the brightness of this light between 0.255."""
if self.entity_description.get_brightness_fn is None:
return None

return round(
255
* (
self.entity_description.get_brightness_fn(self._host.api, self._channel)
/ 100.0
)
)

async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn light off."""
await self.entity_description.turn_on_off_fn(
self._host.api, self._channel, False
)
self.async_write_ha_state()

async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn light on."""
if (
brightness := kwargs.get(ATTR_BRIGHTNESS)
) is not None and self.entity_description.set_brightness_fn is not None:
brightness_pct = int(brightness / 255.0 * 100)
await self.entity_description.set_brightness_fn(
self._host.api, self._channel, brightness_pct
)

await self.entity_description.turn_on_off_fn(
self._host.api, self._channel, True
)
self.async_write_ha_state()
Comment thread
starkillerOG marked this conversation as resolved.