Add Reolink hub status light (#126388)

* Add Home Hub status led

* fix styling

* Add tests
This commit is contained in:
starkillerOG 2024-09-22 14:44:26 +02:00 committed by GitHub
parent 118ceedda1
commit bd3efe57f7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 175 additions and 2 deletions

View file

@ -20,7 +20,12 @@ from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .entity import ReolinkChannelCoordinatorEntity, ReolinkChannelEntityDescription
from .entity import (
ReolinkChannelCoordinatorEntity,
ReolinkChannelEntityDescription,
ReolinkHostCoordinatorEntity,
ReolinkHostEntityDescription,
)
from .util import ReolinkConfigEntry, ReolinkData
@ -37,6 +42,17 @@ class ReolinkLightEntityDescription(
turn_on_off_fn: Callable[[Host, int, bool], Any]
@dataclass(frozen=True, kw_only=True)
class ReolinkHostLightEntityDescription(
LightEntityDescription,
ReolinkHostEntityDescription,
):
"""A class that describes host light entities."""
is_on_fn: Callable[[Host], bool]
turn_on_off_fn: Callable[[Host, bool], Any]
LIGHT_ENTITIES = (
ReolinkLightEntityDescription(
key="floodlight",
@ -59,6 +75,18 @@ LIGHT_ENTITIES = (
),
)
HOST_LIGHT_ENTITIES = (
ReolinkHostLightEntityDescription(
key="hub_status_led",
cmd_key="GetStateLight",
translation_key="status_led",
entity_category=EntityCategory.CONFIG,
supported=lambda api: api.supported(None, "state_light"),
is_on_fn=lambda api: api.state_light,
turn_on_off_fn=lambda api, value: api.set_state_light(value),
),
)
async def async_setup_entry(
hass: HomeAssistant,
@ -68,13 +96,20 @@ async def async_setup_entry(
"""Set up a Reolink light entities."""
reolink_data: ReolinkData = config_entry.runtime_data
async_add_entities(
entities: list[ReolinkLightEntity | ReolinkHostLightEntity] = [
ReolinkLightEntity(reolink_data, channel, entity_description)
for entity_description in LIGHT_ENTITIES
for channel in reolink_data.host.api.channels
if entity_description.supported(reolink_data.host.api, channel)
]
entities.extend(
ReolinkHostLightEntity(reolink_data, entity_description)
for entity_description in HOST_LIGHT_ENTITIES
if entity_description.supported(reolink_data.host.api)
)
async_add_entities(entities)
class ReolinkLightEntity(ReolinkChannelCoordinatorEntity, LightEntity):
"""Base light entity class for Reolink IP cameras."""
@ -148,3 +183,41 @@ class ReolinkLightEntity(ReolinkChannelCoordinatorEntity, LightEntity):
except ReolinkError as err:
raise HomeAssistantError(err) from err
self.async_write_ha_state()
class ReolinkHostLightEntity(ReolinkHostCoordinatorEntity, LightEntity):
"""Base host light entity class for Reolink IP cameras."""
entity_description: ReolinkHostLightEntityDescription
_attr_supported_color_modes = {ColorMode.ONOFF}
_attr_color_mode = ColorMode.ONOFF
def __init__(
self,
reolink_data: ReolinkData,
entity_description: ReolinkHostLightEntityDescription,
) -> None:
"""Initialize Reolink host light entity."""
self.entity_description = entity_description
super().__init__(reolink_data)
@property
def is_on(self) -> bool:
"""Return true if light is on."""
return self.entity_description.is_on_fn(self._host.api)
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn light off."""
try:
await self.entity_description.turn_on_off_fn(self._host.api, False)
except ReolinkError as err:
raise HomeAssistantError(err) from err
self.async_write_ha_state()
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn light on."""
try:
await self.entity_description.turn_on_off_fn(self._host.api, True)
except ReolinkError as err:
raise HomeAssistantError(err) from err
self.async_write_ha_state()

View file

@ -137,6 +137,9 @@
'0': 1,
'null': 2,
}),
'GetStateLight': dict({
'null': 1,
}),
'GetWhiteLed': dict({
'0': 3,
'null': 3,

View file

@ -144,3 +144,100 @@ async def test_light_turn_on(
{ATTR_ENTITY_ID: entity_id, ATTR_BRIGHTNESS: 51},
blocking=True,
)
async def test_host_light_state(
hass: HomeAssistant,
config_entry: MockConfigEntry,
reolink_connect: MagicMock,
) -> None:
"""Test host light entity state with status led."""
reolink_connect.state_light = True
with patch("homeassistant.components.reolink.PLATFORMS", [Platform.LIGHT]):
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
assert config_entry.state is ConfigEntryState.LOADED
entity_id = f"{Platform.LIGHT}.{TEST_NVR_NAME}_status_led"
state = hass.states.get(entity_id)
assert state.state == STATE_ON
async def test_host_light_turn_off(
hass: HomeAssistant,
config_entry: MockConfigEntry,
reolink_connect: MagicMock,
) -> None:
"""Test host light turn off service."""
def mock_supported(ch, capability):
if capability == "power_led":
return False
return True
reolink_connect.supported = mock_supported
with patch("homeassistant.components.reolink.PLATFORMS", [Platform.LIGHT]):
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
assert config_entry.state is ConfigEntryState.LOADED
entity_id = f"{Platform.LIGHT}.{TEST_NVR_NAME}_status_led"
await hass.services.async_call(
LIGHT_DOMAIN,
SERVICE_TURN_OFF,
{ATTR_ENTITY_ID: entity_id},
blocking=True,
)
reolink_connect.set_state_light.assert_called_with(False)
reolink_connect.set_state_light.side_effect = ReolinkError("Test error")
with pytest.raises(HomeAssistantError):
await hass.services.async_call(
LIGHT_DOMAIN,
SERVICE_TURN_OFF,
{ATTR_ENTITY_ID: entity_id},
blocking=True,
)
async def test_host_light_turn_on(
hass: HomeAssistant,
config_entry: MockConfigEntry,
reolink_connect: MagicMock,
) -> None:
"""Test host light turn on service."""
def mock_supported(ch, capability):
if capability == "power_led":
return False
return True
reolink_connect.supported = mock_supported
with patch("homeassistant.components.reolink.PLATFORMS", [Platform.LIGHT]):
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
assert config_entry.state is ConfigEntryState.LOADED
entity_id = f"{Platform.LIGHT}.{TEST_NVR_NAME}_status_led"
await hass.services.async_call(
LIGHT_DOMAIN,
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: entity_id},
blocking=True,
)
reolink_connect.set_state_light.assert_called_with(True)
reolink_connect.set_state_light.side_effect = ReolinkError("Test error")
with pytest.raises(HomeAssistantError):
await hass.services.async_call(
LIGHT_DOMAIN,
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: entity_id},
blocking=True,
)