diff --git a/homeassistant/components/wmspro/__init__.py b/homeassistant/components/wmspro/__init__.py index c0c4a9e3950065..7d2cbf8a3a17a1 100644 --- a/homeassistant/components/wmspro/__init__.py +++ b/homeassistant/components/wmspro/__init__.py @@ -15,7 +15,7 @@ from .const import DOMAIN, MANUFACTURER -PLATFORMS: list[Platform] = [Platform.COVER] +PLATFORMS: list[Platform] = [Platform.COVER, Platform.SCENE] type WebControlProConfigEntry = ConfigEntry[WebControlPro] diff --git a/homeassistant/components/wmspro/scene.py b/homeassistant/components/wmspro/scene.py new file mode 100644 index 00000000000000..b136f70c77a899 --- /dev/null +++ b/homeassistant/components/wmspro/scene.py @@ -0,0 +1,60 @@ +"""Support for scenes provided by WMS WebControl pro.""" + +from __future__ import annotations + +from typing import Any + +from wmspro.scene import Scene as WMS_Scene + +from homeassistant.components.scene import Scene +from homeassistant.core import HomeAssistant +from homeassistant.helpers.device_registry import DeviceInfo +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from . import WebControlProConfigEntry +from .const import ATTRIBUTION, DOMAIN, MANUFACTURER + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: WebControlProConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up the WMS based scenes from a config entry.""" + hub = config_entry.runtime_data + + entities: list[WebControlProScene] = [ + WebControlProScene(config_entry.entry_id, scene) + for scene in hub.scenes.values() + ] + + async_add_entities(entities) + + +class WebControlProScene(Scene): + """Representation of a WMS based scene.""" + + _attr_attribution = ATTRIBUTION + _attr_has_entity_name = True + _attr_name = None + + def __init__(self, config_entry_id: str, scene: WMS_Scene) -> None: + """Initialize the entity with the configured scene.""" + super().__init__() + scene_id_str = str(scene.id) + self._scene = scene + self._attr_unique_id = scene_id_str + self._attr_device_info = DeviceInfo( + identifiers={(DOMAIN, scene_id_str)}, + manufacturer=MANUFACTURER, + model="Scene", + name=scene.name, + serial_number=scene_id_str, + suggested_area=scene.room.name, + via_device=(DOMAIN, config_entry_id), + configuration_url=f"http://{scene.host}/control", + ) + + async def async_activate(self, **kwargs: Any) -> None: + """Activate scene. Try to get entities into requested state.""" + await self._scene() diff --git a/tests/components/wmspro/conftest.py b/tests/components/wmspro/conftest.py index 76c11e71316bb4..0e0b31b0117153 100644 --- a/tests/components/wmspro/conftest.py +++ b/tests/components/wmspro/conftest.py @@ -104,3 +104,12 @@ async def fake_call(self, **kwargs): fake_call, ) as mock_action_call: yield mock_action_call + + +@pytest.fixture +def mock_scene_call() -> Generator[AsyncMock]: + """Override Scene.__call__.""" + with patch( + "wmspro.scene.Scene.__call__", + ) as mock_scene_call: + yield mock_scene_call diff --git a/tests/components/wmspro/snapshots/test_scene.ambr b/tests/components/wmspro/snapshots/test_scene.ambr new file mode 100644 index 00000000000000..1bf29a78f8f6d7 --- /dev/null +++ b/tests/components/wmspro/snapshots/test_scene.ambr @@ -0,0 +1,47 @@ +# serializer version: 1 +# name: test_scene_activate + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by WMS WebControl pro API', + 'friendly_name': 'Gute Nacht', + }), + 'context': , + 'entity_id': 'scene.gute_nacht', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_scene_device + DeviceRegistryEntrySnapshot({ + 'area_id': 'raum_0', + 'config_entries': , + 'configuration_url': 'http://webcontrol/control', + 'connections': set({ + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': None, + 'id': , + 'identifiers': set({ + tuple( + 'wmspro', + '688966', + ), + }), + 'is_new': False, + 'labels': set({ + }), + 'manufacturer': 'WAREMA Renkhoff SE', + 'model': 'Scene', + 'model_id': None, + 'name': 'Gute Nacht', + 'name_by_user': None, + 'primary_config_entry': , + 'serial_number': '688966', + 'suggested_area': 'Raum 0', + 'sw_version': None, + 'via_device_id': , + }) +# --- diff --git a/tests/components/wmspro/test_cover.py b/tests/components/wmspro/test_cover.py index 1e8653335a7ee2..83662e6b728176 100644 --- a/tests/components/wmspro/test_cover.py +++ b/tests/components/wmspro/test_cover.py @@ -1,4 +1,4 @@ -"""Test the wmspro diagnostics.""" +"""Test the wmspro cover support.""" from unittest.mock import AsyncMock, patch diff --git a/tests/components/wmspro/test_scene.py b/tests/components/wmspro/test_scene.py new file mode 100644 index 00000000000000..151f2f1591d1c2 --- /dev/null +++ b/tests/components/wmspro/test_scene.py @@ -0,0 +1,63 @@ +"""Test the wmspro scene support.""" + +from unittest.mock import AsyncMock + +from syrupy import SnapshotAssertion + +from homeassistant.components.wmspro.const import DOMAIN +from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_ON +from homeassistant.core import HomeAssistant +from homeassistant.helpers import device_registry as dr +from homeassistant.setup import async_setup_component + +from . import setup_config_entry + +from tests.common import MockConfigEntry + + +async def test_scene_device( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_hub_ping: AsyncMock, + mock_hub_configuration_test: AsyncMock, + mock_dest_refresh: AsyncMock, + device_registry: dr.DeviceRegistry, + snapshot: SnapshotAssertion, +) -> None: + """Test that a scene device is created correctly.""" + assert await setup_config_entry(hass, mock_config_entry) + assert len(mock_hub_ping.mock_calls) == 1 + assert len(mock_hub_configuration_test.mock_calls) == 1 + + device_entry = device_registry.async_get_device(identifiers={(DOMAIN, "688966")}) + assert device_entry is not None + assert device_entry == snapshot + + +async def test_scene_activate( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_hub_ping: AsyncMock, + mock_hub_configuration_test: AsyncMock, + mock_dest_refresh: AsyncMock, + mock_scene_call: AsyncMock, + snapshot: SnapshotAssertion, +) -> None: + """Test that a scene entity is created and activated correctly.""" + assert await setup_config_entry(hass, mock_config_entry) + assert len(mock_hub_ping.mock_calls) == 1 + assert len(mock_hub_configuration_test.mock_calls) == 1 + + entity = hass.states.get("scene.gute_nacht") + assert entity is not None + assert entity == snapshot + + await async_setup_component(hass, "homeassistant", {}) + await hass.services.async_call( + "homeassistant", + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: entity.entity_id}, + blocking=True, + ) + + assert len(mock_scene_call.mock_calls) == 1