Add cover to the niko_home_control integration (#133801)

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
pull/133900/head
Glenn Vandeuren (aka Iondependent) 2024-12-23 15:47:09 +01:00 committed by GitHub
parent 70648da8fd
commit 43a420cf01
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 275 additions and 8 deletions

View File

@ -13,7 +13,7 @@ from homeassistant.helpers import entity_registry as er
from .const import _LOGGER
PLATFORMS: list[Platform] = [Platform.LIGHT]
PLATFORMS: list[Platform] = [Platform.COVER, Platform.LIGHT]
type NikoHomeControlConfigEntry = ConfigEntry[NHCController]

View File

@ -0,0 +1,54 @@
"""Cover Platform for Niko Home Control."""
from __future__ import annotations
from typing import Any
from nhc.cover import NHCCover
from homeassistant.components.cover import CoverEntity, CoverEntityFeature
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import NikoHomeControlConfigEntry
from .entity import NikoHomeControlEntity
async def async_setup_entry(
hass: HomeAssistant,
entry: NikoHomeControlConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the Niko Home Control cover entry."""
controller = entry.runtime_data
async_add_entities(
NikoHomeControlCover(cover, controller, entry.entry_id)
for cover in controller.covers
)
class NikoHomeControlCover(NikoHomeControlEntity, CoverEntity):
"""Representation of a Niko Cover."""
_attr_name = None
_attr_supported_features: CoverEntityFeature = (
CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE | CoverEntityFeature.STOP
)
_action: NHCCover
def open_cover(self, **kwargs: Any) -> None:
"""Open the cover."""
self._action.open()
def close_cover(self, **kwargs: Any) -> None:
"""Close the cover."""
self._action.close()
def stop_cover(self, **kwargs: Any) -> None:
"""Stop the cover."""
self._action.stop()
def update_state(self):
"""Update HA state."""
self._attr_is_closed = self._action.state == 0

View File

@ -1,5 +1,10 @@
"""Tests for the niko_home_control integration."""
from collections.abc import Awaitable, Callable
from unittest.mock import AsyncMock
import pytest
from homeassistant.core import HomeAssistant
from tests.common import MockConfigEntry
@ -11,3 +16,13 @@ async def setup_integration(hass: HomeAssistant, config_entry: MockConfigEntry)
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
def find_update_callback(
mock: AsyncMock, identifier: int
) -> Callable[[int], Awaitable[None]]:
"""Find the update callback for a specific identifier."""
for call in mock.register_callback.call_args_list:
if call[0][0] == identifier:
return call[0][1]
pytest.fail(f"Callback for identifier {identifier} not found")

View File

@ -3,6 +3,7 @@
from collections.abc import Generator
from unittest.mock import AsyncMock, patch
from nhc.cover import NHCCover
from nhc.light import NHCLight
import pytest
@ -48,9 +49,21 @@ def dimmable_light() -> NHCLight:
return mock
@pytest.fixture
def cover() -> NHCCover:
"""Return a cover mock."""
mock = AsyncMock(spec=NHCCover)
mock.id = 3
mock.type = 4
mock.name = "cover"
mock.suggested_area = "room"
mock.state = 100
return mock
@pytest.fixture
def mock_niko_home_control_connection(
light: NHCLight, dimmable_light: NHCLight
light: NHCLight, dimmable_light: NHCLight, cover: NHCCover
) -> Generator[AsyncMock]:
"""Mock a NHC client."""
with (
@ -65,6 +78,7 @@ def mock_niko_home_control_connection(
):
client = mock_client.return_value
client.lights = [light, dimmable_light]
client.covers = [cover]
yield client

View File

@ -0,0 +1,48 @@
# serializer version: 1
# name: test_cover[cover.cover-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'cover',
'entity_category': None,
'entity_id': 'cover.cover',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': None,
'platform': 'niko_home_control',
'previous_unique_id': None,
'supported_features': <CoverEntityFeature: 11>,
'translation_key': None,
'unique_id': '01JFN93M7KRA38V5AMPCJ2JYYV-3',
'unit_of_measurement': None,
})
# ---
# name: test_cover[cover.cover-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'cover',
'supported_features': <CoverEntityFeature: 11>,
}),
'context': <ANY>,
'entity_id': 'cover.cover',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'open',
})
# ---

View File

@ -0,0 +1,138 @@
"""Tests for the Niko Home Control cover platform."""
from unittest.mock import AsyncMock, patch
import pytest
from syrupy import SnapshotAssertion
from homeassistant.components.cover import DOMAIN as COVER_DOMAIN
from homeassistant.const import (
ATTR_ENTITY_ID,
SERVICE_CLOSE_COVER,
SERVICE_OPEN_COVER,
SERVICE_STOP_COVER,
STATE_CLOSED,
STATE_OPEN,
Platform,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from . import find_update_callback, setup_integration
from tests.common import MockConfigEntry, snapshot_platform
async def test_cover(
hass: HomeAssistant,
snapshot: SnapshotAssertion,
mock_niko_home_control_connection: AsyncMock,
mock_config_entry: MockConfigEntry,
entity_registry: er.EntityRegistry,
) -> None:
"""Test all entities."""
with patch(
"homeassistant.components.niko_home_control.PLATFORMS", [Platform.COVER]
):
await setup_integration(hass, mock_config_entry)
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
@pytest.mark.parametrize(
("cover_id", "entity_id"),
[
(0, "cover.cover"),
],
)
async def test_open_cover(
hass: HomeAssistant,
mock_niko_home_control_connection: AsyncMock,
mock_config_entry: MockConfigEntry,
cover_id: int,
entity_id: int,
) -> None:
"""Test opening the cover."""
await setup_integration(hass, mock_config_entry)
await hass.services.async_call(
COVER_DOMAIN,
SERVICE_OPEN_COVER,
{ATTR_ENTITY_ID: entity_id},
blocking=True,
)
mock_niko_home_control_connection.covers[cover_id].open.assert_called_once_with()
@pytest.mark.parametrize(
("cover_id", "entity_id"),
[
(0, "cover.cover"),
],
)
async def test_close_cover(
hass: HomeAssistant,
mock_niko_home_control_connection: AsyncMock,
mock_config_entry: MockConfigEntry,
cover_id: int,
entity_id: str,
) -> None:
"""Test closing the cover."""
await setup_integration(hass, mock_config_entry)
await hass.services.async_call(
COVER_DOMAIN,
SERVICE_CLOSE_COVER,
{ATTR_ENTITY_ID: entity_id},
blocking=True,
)
mock_niko_home_control_connection.covers[cover_id].close.assert_called_once_with()
@pytest.mark.parametrize(
("cover_id", "entity_id"),
[
(0, "cover.cover"),
],
)
async def test_stop_cover(
hass: HomeAssistant,
mock_niko_home_control_connection: AsyncMock,
mock_config_entry: MockConfigEntry,
cover_id: int,
entity_id: str,
) -> None:
"""Test closing the cover."""
await setup_integration(hass, mock_config_entry)
await hass.services.async_call(
COVER_DOMAIN,
SERVICE_STOP_COVER,
{ATTR_ENTITY_ID: entity_id},
blocking=True,
)
mock_niko_home_control_connection.covers[cover_id].stop.assert_called_once_with()
async def test_updating(
hass: HomeAssistant,
mock_niko_home_control_connection: AsyncMock,
mock_config_entry: MockConfigEntry,
cover: AsyncMock,
) -> None:
"""Test closing the cover."""
await setup_integration(hass, mock_config_entry)
assert hass.states.get("cover.cover").state == STATE_OPEN
cover.state = 0
await find_update_callback(mock_niko_home_control_connection, 3)(0)
await hass.async_block_till_done()
assert hass.states.get("cover.cover").state == STATE_CLOSED
cover.state = 100
await find_update_callback(mock_niko_home_control_connection, 3)(100)
await hass.async_block_till_done()
assert hass.states.get("cover.cover").state == STATE_OPEN

View File

@ -18,7 +18,7 @@ from homeassistant.const import (
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from . import setup_integration
from . import find_update_callback, setup_integration
from tests.common import MockConfigEntry, snapshot_platform
@ -113,7 +113,7 @@ async def test_updating(
assert hass.states.get("light.light").state == STATE_ON
light.state = 0
await mock_niko_home_control_connection.register_callback.call_args_list[0][0][1](0)
await find_update_callback(mock_niko_home_control_connection, 1)(0)
await hass.async_block_till_done()
assert hass.states.get("light.light").state == STATE_OFF
@ -122,16 +122,14 @@ async def test_updating(
assert hass.states.get("light.dimmable_light").attributes[ATTR_BRIGHTNESS] == 255
dimmable_light.state = 80
await mock_niko_home_control_connection.register_callback.call_args_list[1][0][1](
80
)
await find_update_callback(mock_niko_home_control_connection, 2)(80)
await hass.async_block_till_done()
assert hass.states.get("light.dimmable_light").state == STATE_ON
assert hass.states.get("light.dimmable_light").attributes[ATTR_BRIGHTNESS] == 204
dimmable_light.state = 0
await mock_niko_home_control_connection.register_callback.call_args_list[1][0][1](0)
await find_update_callback(mock_niko_home_control_connection, 2)(0)
await hass.async_block_till_done()
assert hass.states.get("light.dimmable_light").state == STATE_OFF