Add Reolink hub volume number entities (#126389)
* Add Home Hub alarm and message volume * fix styling * Add tests * Update homeassistant/components/reolink/number.py * Update test_diagnostics.ambr --------- Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
This commit is contained in:
parent
286c22c0ed
commit
90957dfedb
5 changed files with 142 additions and 2 deletions
|
@ -106,6 +106,18 @@
|
|||
"0": "mdi:volume-off"
|
||||
}
|
||||
},
|
||||
"alarm_volume": {
|
||||
"default": "mdi:volume-high",
|
||||
"state": {
|
||||
"0": "mdi:volume-off"
|
||||
}
|
||||
},
|
||||
"message_volume": {
|
||||
"default": "mdi:volume-high",
|
||||
"state": {
|
||||
"0": "mdi:volume-off"
|
||||
}
|
||||
},
|
||||
"guard_return_time": {
|
||||
"default": "mdi:crosshairs-gps"
|
||||
},
|
||||
|
|
|
@ -24,6 +24,8 @@ from .entity import (
|
|||
ReolinkChannelEntityDescription,
|
||||
ReolinkChimeCoordinatorEntity,
|
||||
ReolinkChimeEntityDescription,
|
||||
ReolinkHostCoordinatorEntity,
|
||||
ReolinkHostEntityDescription,
|
||||
)
|
||||
from .util import ReolinkConfigEntry, ReolinkData
|
||||
|
||||
|
@ -42,6 +44,18 @@ class ReolinkNumberEntityDescription(
|
|||
value: Callable[[Host, int], float | None]
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class ReolinkHostNumberEntityDescription(
|
||||
NumberEntityDescription,
|
||||
ReolinkHostEntityDescription,
|
||||
):
|
||||
"""A class that describes number entities for the host."""
|
||||
|
||||
method: Callable[[Host, float], Any]
|
||||
mode: NumberMode = NumberMode.AUTO
|
||||
value: Callable[[Host], float | None]
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class ReolinkChimeNumberEntityDescription(
|
||||
NumberEntityDescription,
|
||||
|
@ -474,6 +488,33 @@ NUMBER_ENTITIES = (
|
|||
),
|
||||
)
|
||||
|
||||
HOST_NUMBER_ENTITIES = (
|
||||
ReolinkHostNumberEntityDescription(
|
||||
key="alarm_volume",
|
||||
cmd_key="GetDeviceAudioCfg",
|
||||
translation_key="alarm_volume",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
native_step=1,
|
||||
native_min_value=0,
|
||||
native_max_value=100,
|
||||
supported=lambda api: api.supported(None, "hub_audio"),
|
||||
value=lambda api: api.alarm_volume,
|
||||
method=lambda api, value: api.set_hub_audio(alarm_volume=int(value)),
|
||||
),
|
||||
ReolinkHostNumberEntityDescription(
|
||||
key="message_volume",
|
||||
cmd_key="GetDeviceAudioCfg",
|
||||
translation_key="message_volume",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
native_step=1,
|
||||
native_min_value=0,
|
||||
native_max_value=100,
|
||||
supported=lambda api: api.supported(None, "hub_audio"),
|
||||
value=lambda api: api.message_volume,
|
||||
method=lambda api, value: api.set_hub_audio(message_volume=int(value)),
|
||||
),
|
||||
)
|
||||
|
||||
CHIME_NUMBER_ENTITIES = (
|
||||
ReolinkChimeNumberEntityDescription(
|
||||
key="volume",
|
||||
|
@ -497,12 +538,17 @@ async def async_setup_entry(
|
|||
"""Set up a Reolink number entities."""
|
||||
reolink_data: ReolinkData = config_entry.runtime_data
|
||||
|
||||
entities: list[ReolinkNumberEntity | ReolinkChimeNumberEntity] = [
|
||||
entities: list[NumberEntity] = [
|
||||
ReolinkNumberEntity(reolink_data, channel, entity_description)
|
||||
for entity_description in NUMBER_ENTITIES
|
||||
for channel in reolink_data.host.api.channels
|
||||
if entity_description.supported(reolink_data.host.api, channel)
|
||||
]
|
||||
entities.extend(
|
||||
ReolinkHostNumberEntity(reolink_data, entity_description)
|
||||
for entity_description in HOST_NUMBER_ENTITIES
|
||||
if entity_description.supported(reolink_data.host.api)
|
||||
)
|
||||
entities.extend(
|
||||
ReolinkChimeNumberEntity(reolink_data, chime, entity_description)
|
||||
for entity_description in CHIME_NUMBER_ENTITIES
|
||||
|
@ -552,6 +598,38 @@ class ReolinkNumberEntity(ReolinkChannelCoordinatorEntity, NumberEntity):
|
|||
self.async_write_ha_state()
|
||||
|
||||
|
||||
class ReolinkHostNumberEntity(ReolinkHostCoordinatorEntity, NumberEntity):
|
||||
"""Base number entity class for Reolink Host."""
|
||||
|
||||
entity_description: ReolinkHostNumberEntityDescription
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
reolink_data: ReolinkData,
|
||||
entity_description: ReolinkHostNumberEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize Reolink number entity."""
|
||||
self.entity_description = entity_description
|
||||
super().__init__(reolink_data)
|
||||
|
||||
self._attr_mode = entity_description.mode
|
||||
|
||||
@property
|
||||
def native_value(self) -> float | None:
|
||||
"""State of the number entity."""
|
||||
return self.entity_description.value(self._host.api)
|
||||
|
||||
async def async_set_native_value(self, value: float) -> None:
|
||||
"""Update the current value."""
|
||||
try:
|
||||
await self.entity_description.method(self._host.api, value)
|
||||
except InvalidParameterError as err:
|
||||
raise ServiceValidationError(err) from err
|
||||
except ReolinkError as err:
|
||||
raise HomeAssistantError(err) from err
|
||||
self.async_write_ha_state()
|
||||
|
||||
|
||||
class ReolinkChimeNumberEntity(ReolinkChimeCoordinatorEntity, NumberEntity):
|
||||
"""Base number entity class for Reolink IP cameras."""
|
||||
|
||||
|
|
|
@ -395,6 +395,12 @@
|
|||
"volume": {
|
||||
"name": "Volume"
|
||||
},
|
||||
"alarm_volume": {
|
||||
"name": "Alarm volume"
|
||||
},
|
||||
"message_volume": {
|
||||
"name": "Message volume"
|
||||
},
|
||||
"guard_return_time": {
|
||||
"name": "Guard return time"
|
||||
},
|
||||
|
|
|
@ -79,7 +79,7 @@
|
|||
}),
|
||||
'GetDeviceAudioCfg': dict({
|
||||
'0': 2,
|
||||
'null': 2,
|
||||
'null': 4,
|
||||
}),
|
||||
'GetEmail': dict({
|
||||
'0': 1,
|
||||
|
|
|
@ -65,6 +65,50 @@ async def test_number(
|
|||
)
|
||||
|
||||
|
||||
async def test_host_number(
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
reolink_connect: MagicMock,
|
||||
) -> None:
|
||||
"""Test number entity with volume."""
|
||||
reolink_connect.alarm_volume = 85
|
||||
|
||||
with patch("homeassistant.components.reolink.PLATFORMS", [Platform.NUMBER]):
|
||||
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.NUMBER}.{TEST_NVR_NAME}_alarm_volume"
|
||||
|
||||
assert hass.states.get(entity_id).state == "85"
|
||||
|
||||
await hass.services.async_call(
|
||||
NUMBER_DOMAIN,
|
||||
SERVICE_SET_VALUE,
|
||||
{ATTR_ENTITY_ID: entity_id, ATTR_VALUE: 45},
|
||||
blocking=True,
|
||||
)
|
||||
reolink_connect.set_hub_audio.assert_called_with(alarm_volume=45)
|
||||
|
||||
reolink_connect.set_hub_audio.side_effect = ReolinkError("Test error")
|
||||
with pytest.raises(HomeAssistantError):
|
||||
await hass.services.async_call(
|
||||
NUMBER_DOMAIN,
|
||||
SERVICE_SET_VALUE,
|
||||
{ATTR_ENTITY_ID: entity_id, ATTR_VALUE: 45},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
reolink_connect.set_hub_audio.side_effect = InvalidParameterError("Test error")
|
||||
with pytest.raises(HomeAssistantError):
|
||||
await hass.services.async_call(
|
||||
NUMBER_DOMAIN,
|
||||
SERVICE_SET_VALUE,
|
||||
{ATTR_ENTITY_ID: entity_id, ATTR_VALUE: 45},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
|
||||
async def test_chime_number(
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
|
|
Loading…
Add table
Reference in a new issue