mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 11:17:21 +00:00
Add Reolink home hub scene select entity (#140823)
This commit is contained in:
parent
245f0a1958
commit
334359871d
@ -350,6 +350,9 @@
|
||||
},
|
||||
"sub_bit_rate": {
|
||||
"default": "mdi:play-speed"
|
||||
},
|
||||
"scene_mode": {
|
||||
"default": "mdi:view-list"
|
||||
}
|
||||
},
|
||||
"sensor": {
|
||||
|
@ -30,6 +30,8 @@ from .entity import (
|
||||
ReolinkChannelEntityDescription,
|
||||
ReolinkChimeCoordinatorEntity,
|
||||
ReolinkChimeEntityDescription,
|
||||
ReolinkHostCoordinatorEntity,
|
||||
ReolinkHostEntityDescription,
|
||||
)
|
||||
from .util import ReolinkConfigEntry, ReolinkData, raise_translated_error
|
||||
|
||||
@ -49,6 +51,18 @@ class ReolinkSelectEntityDescription(
|
||||
value: Callable[[Host, int], str] | None = None
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class ReolinkHostSelectEntityDescription(
|
||||
SelectEntityDescription,
|
||||
ReolinkHostEntityDescription,
|
||||
):
|
||||
"""A class that describes host select entities."""
|
||||
|
||||
get_options: Callable[[Host], list[str]]
|
||||
method: Callable[[Host, str], Any]
|
||||
value: Callable[[Host], str]
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class ReolinkChimeSelectEntityDescription(
|
||||
SelectEntityDescription,
|
||||
@ -238,6 +252,19 @@ SELECT_ENTITIES = (
|
||||
),
|
||||
)
|
||||
|
||||
HOST_SELECT_ENTITIES = (
|
||||
ReolinkHostSelectEntityDescription(
|
||||
key="scene_mode",
|
||||
cmd_key="GetScene",
|
||||
translation_key="scene_mode",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
get_options=lambda api: api.baichuan.scene_names,
|
||||
supported=lambda api: api.supported(None, "scenes"),
|
||||
value=lambda api: api.baichuan.active_scene,
|
||||
method=lambda api, name: api.baichuan.set_scene(scene_name=name),
|
||||
),
|
||||
)
|
||||
|
||||
CHIME_SELECT_ENTITIES = (
|
||||
ReolinkChimeSelectEntityDescription(
|
||||
key="motion_tone",
|
||||
@ -300,12 +327,19 @@ async def async_setup_entry(
|
||||
"""Set up a Reolink select entities."""
|
||||
reolink_data: ReolinkData = config_entry.runtime_data
|
||||
|
||||
entities: list[ReolinkSelectEntity | ReolinkChimeSelectEntity] = [
|
||||
entities: list[
|
||||
ReolinkSelectEntity | ReolinkHostSelectEntity | ReolinkChimeSelectEntity
|
||||
] = [
|
||||
ReolinkSelectEntity(reolink_data, channel, entity_description)
|
||||
for entity_description in SELECT_ENTITIES
|
||||
for channel in reolink_data.host.api.channels
|
||||
if entity_description.supported(reolink_data.host.api, channel)
|
||||
]
|
||||
entities.extend(
|
||||
ReolinkHostSelectEntity(reolink_data, entity_description)
|
||||
for entity_description in HOST_SELECT_ENTITIES
|
||||
if entity_description.supported(reolink_data.host.api)
|
||||
)
|
||||
entities.extend(
|
||||
ReolinkChimeSelectEntity(reolink_data, chime, entity_description)
|
||||
for entity_description in CHIME_SELECT_ENTITIES
|
||||
@ -360,6 +394,33 @@ class ReolinkSelectEntity(ReolinkChannelCoordinatorEntity, SelectEntity):
|
||||
self.async_write_ha_state()
|
||||
|
||||
|
||||
class ReolinkHostSelectEntity(ReolinkHostCoordinatorEntity, SelectEntity):
|
||||
"""Base select entity class for Reolink Host."""
|
||||
|
||||
entity_description: ReolinkHostSelectEntityDescription
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
reolink_data: ReolinkData,
|
||||
entity_description: ReolinkHostSelectEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize Reolink select entity."""
|
||||
self.entity_description = entity_description
|
||||
super().__init__(reolink_data)
|
||||
self._attr_options = entity_description.get_options(self._host.api)
|
||||
|
||||
@property
|
||||
def current_option(self) -> str | None:
|
||||
"""Return the current option."""
|
||||
return self.entity_description.value(self._host.api)
|
||||
|
||||
@raise_translated_error
|
||||
async def async_select_option(self, option: str) -> None:
|
||||
"""Change the selected option."""
|
||||
await self.entity_description.method(self._host.api, option)
|
||||
self.async_write_ha_state()
|
||||
|
||||
|
||||
class ReolinkChimeSelectEntity(ReolinkChimeCoordinatorEntity, SelectEntity):
|
||||
"""Base select entity class for Reolink IP cameras."""
|
||||
|
||||
|
@ -799,6 +799,15 @@
|
||||
},
|
||||
"sub_bit_rate": {
|
||||
"name": "Fluent bit rate"
|
||||
},
|
||||
"scene_mode": {
|
||||
"name": "Scene mode",
|
||||
"state": {
|
||||
"off": "[%key:common::state::off%]",
|
||||
"disarm": "Disarmed",
|
||||
"home": "Home",
|
||||
"away": "Away"
|
||||
}
|
||||
}
|
||||
},
|
||||
"sensor": {
|
||||
|
@ -145,6 +145,8 @@ def reolink_connect_class() -> Generator[MagicMock]:
|
||||
host_mock.baichuan.privacy_mode.return_value = False
|
||||
host_mock.baichuan.day_night_state.return_value = "day"
|
||||
host_mock.baichuan.subscribe_events.side_effect = ReolinkError("Test error")
|
||||
host_mock.baichuan.active_scene = "off"
|
||||
host_mock.baichuan.scene_names = ["off", "home"]
|
||||
host_mock.baichuan.abilities = {
|
||||
0: {"chnID": 0, "aitype": 34615},
|
||||
"Host": {"pushAlarm": 7},
|
||||
|
@ -170,6 +170,9 @@
|
||||
'0': 1,
|
||||
'null': 2,
|
||||
}),
|
||||
'GetScene': dict({
|
||||
'null': 1,
|
||||
}),
|
||||
'GetStateLight': dict({
|
||||
'null': 1,
|
||||
}),
|
||||
|
@ -104,6 +104,58 @@ async def test_play_quick_reply_message(
|
||||
reolink_connect.quick_reply_dict = MagicMock()
|
||||
|
||||
|
||||
async def test_host_scene_select(
|
||||
hass: HomeAssistant,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
config_entry: MockConfigEntry,
|
||||
reolink_connect: MagicMock,
|
||||
) -> None:
|
||||
"""Test host select entity with scene mode."""
|
||||
with patch("homeassistant.components.reolink.PLATFORMS", [Platform.SELECT]):
|
||||
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.SELECT}.{TEST_NVR_NAME}_scene_mode"
|
||||
assert hass.states.get(entity_id).state == "off"
|
||||
|
||||
await hass.services.async_call(
|
||||
SELECT_DOMAIN,
|
||||
SERVICE_SELECT_OPTION,
|
||||
{ATTR_ENTITY_ID: entity_id, "option": "home"},
|
||||
blocking=True,
|
||||
)
|
||||
reolink_connect.baichuan.set_scene.assert_called_once()
|
||||
|
||||
reolink_connect.baichuan.set_scene.side_effect = ReolinkError("Test error")
|
||||
with pytest.raises(HomeAssistantError):
|
||||
await hass.services.async_call(
|
||||
SELECT_DOMAIN,
|
||||
SERVICE_SELECT_OPTION,
|
||||
{ATTR_ENTITY_ID: entity_id, "option": "home"},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
reolink_connect.baichuan.set_scene.side_effect = InvalidParameterError("Test error")
|
||||
with pytest.raises(ServiceValidationError):
|
||||
await hass.services.async_call(
|
||||
SELECT_DOMAIN,
|
||||
SERVICE_SELECT_OPTION,
|
||||
{ATTR_ENTITY_ID: entity_id, "option": "home"},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
reolink_connect.baichuan.active_scene = "Invalid value"
|
||||
freezer.tick(DEVICE_UPDATE_INTERVAL)
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert hass.states.get(entity_id).state == STATE_UNKNOWN
|
||||
|
||||
reolink_connect.baichuan.set_scene.reset_mock(side_effect=True)
|
||||
reolink_connect.baichuan.active_scene = "off"
|
||||
|
||||
|
||||
async def test_chime_select(
|
||||
hass: HomeAssistant,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
|
Loading…
x
Reference in New Issue
Block a user