1
0
mirror of https://github.com/home-assistant/core.git synced 2026-02-15 07:36:16 +00:00

Add scene platform support to Niko Home Control integration (#152712)

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
This commit is contained in:
Glenn Vandeuren (aka Iondependent)
2025-10-24 18:02:37 +02:00
committed by GitHub
parent e8227baa50
commit a48b915d90
5 changed files with 187 additions and 2 deletions

View File

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

View File

@@ -0,0 +1,40 @@
"""Scene Platform for Niko Home Control."""
from __future__ import annotations
from typing import Any
from homeassistant.components.scene import BaseScene
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import NikoHomeControlConfigEntry
from .entity import NikoHomeControlEntity
async def async_setup_entry(
hass: HomeAssistant,
entry: NikoHomeControlConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the Niko Home Control scene entry."""
controller = entry.runtime_data
async_add_entities(
NikoHomeControlScene(scene, controller, entry.entry_id)
for scene in controller.scenes
)
class NikoHomeControlScene(NikoHomeControlEntity, BaseScene):
"""Representation of a Niko Home Control Scene."""
_attr_name = None
async def _async_activate(self, **kwargs: Any) -> None:
"""Activate scene. Try to get entities into requested state."""
await self._action.activate()
def update_state(self) -> None:
"""Update HA state."""
self._async_record_activation()

View File

@@ -5,6 +5,7 @@ from unittest.mock import AsyncMock, patch
from nhc.cover import NHCCover
from nhc.light import NHCLight
from nhc.scene import NHCScene
import pytest
from homeassistant.components.niko_home_control.const import DOMAIN
@@ -61,9 +62,21 @@ def cover() -> NHCCover:
return mock
@pytest.fixture
def scene() -> NHCScene:
"""Return a scene mock."""
mock = AsyncMock(spec=NHCScene)
mock.id = 4
mock.type = 0
mock.name = "scene"
mock.suggested_area = "room"
mock.state = 0
return mock
@pytest.fixture
def mock_niko_home_control_connection(
light: NHCLight, dimmable_light: NHCLight, cover: NHCCover
light: NHCLight, dimmable_light: NHCLight, cover: NHCCover, scene: NHCScene
) -> Generator[AsyncMock]:
"""Mock a NHC client."""
with (
@@ -79,6 +92,7 @@ def mock_niko_home_control_connection(
client = mock_client.return_value
client.lights = [light, dimmable_light]
client.covers = [cover]
client.scenes = [scene]
client.connect = AsyncMock(return_value=True)
yield client

View File

@@ -0,0 +1,49 @@
# serializer version: 1
# name: test_entities[scene.scene-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'scene',
'entity_category': None,
'entity_id': 'scene.scene',
'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,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'unique_id': '01JFN93M7KRA38V5AMPCJ2JYYV-4',
'unit_of_measurement': None,
})
# ---
# name: test_entities[scene.scene-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'scene',
}),
'context': <ANY>,
'entity_id': 'scene.scene',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '2025-10-10T21:00:00+00:00',
})
# ---

View File

@@ -0,0 +1,82 @@
"""Tests for the Niko Home Control Scene platform."""
from unittest.mock import AsyncMock, patch
import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant.components.scene import DOMAIN as SCENE_DOMAIN
from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_ON, 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
@pytest.mark.freeze_time("2025-10-10 21:00:00")
async def test_entities(
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.SCENE]
):
await setup_integration(hass, mock_config_entry)
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
@pytest.mark.parametrize("scene_id", [0])
async def test_activate_scene(
hass: HomeAssistant,
mock_niko_home_control_connection: AsyncMock,
mock_config_entry: MockConfigEntry,
scene_id: int,
entity_registry: er.EntityRegistry,
) -> None:
"""Test activating the scene."""
await setup_integration(hass, mock_config_entry)
await hass.services.async_call(
SCENE_DOMAIN,
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: "scene.scene"},
blocking=True,
)
mock_niko_home_control_connection.scenes[scene_id].activate.assert_called_once()
async def test_updating(
hass: HomeAssistant,
mock_niko_home_control_connection: AsyncMock,
mock_config_entry: MockConfigEntry,
scene: AsyncMock,
) -> None:
"""Test scene state recording after activation."""
await setup_integration(hass, mock_config_entry)
# Resolve the created scene entity dynamically
entity_entries = er.async_entries_for_config_entry(
er.async_get(hass), mock_config_entry.entry_id
)
scene_entities = [e for e in entity_entries if e.domain == SCENE_DOMAIN]
assert scene_entities, "No scene entities registered"
entity_id = scene_entities[0].entity_id
# Capture current state (could be unknown or a timestamp depending on implementation)
before = hass.states.get(entity_id)
assert before is not None
# Simulate a device-originated update for the scene (controller callback)
await find_update_callback(mock_niko_home_control_connection, scene.id)(0)
await hass.async_block_till_done()
after = hass.states.get(entity_id)
assert after is not None
assert after.state != before.state