From 11c6998bf2d6d6c09f31d2ac9a43345ea86f2eda Mon Sep 17 00:00:00 2001 From: Markus Adrario Date: Tue, 27 May 2025 09:48:59 +0200 Subject: [PATCH] Add homee siren platform (#145675) * port siren.py from custom component * Add Siren Tests * last small nits --- homeassistant/components/homee/__init__.py | 1 + homeassistant/components/homee/siren.py | 49 +++++++++++ tests/components/homee/fixtures/siren.json | 52 +++++++++++ .../homee/snapshots/test_siren.ambr | 50 +++++++++++ tests/components/homee/test_siren.py | 86 +++++++++++++++++++ 5 files changed, 238 insertions(+) create mode 100644 homeassistant/components/homee/siren.py create mode 100644 tests/components/homee/fixtures/siren.json create mode 100644 tests/components/homee/snapshots/test_siren.ambr create mode 100644 tests/components/homee/test_siren.py diff --git a/homeassistant/components/homee/__init__.py b/homeassistant/components/homee/__init__.py index 83705d4fed1..e9eb1d86f02 100644 --- a/homeassistant/components/homee/__init__.py +++ b/homeassistant/components/homee/__init__.py @@ -27,6 +27,7 @@ PLATFORMS = [ Platform.NUMBER, Platform.SELECT, Platform.SENSOR, + Platform.SIREN, Platform.SWITCH, Platform.VALVE, ] diff --git a/homeassistant/components/homee/siren.py b/homeassistant/components/homee/siren.py new file mode 100644 index 00000000000..da158c82f46 --- /dev/null +++ b/homeassistant/components/homee/siren.py @@ -0,0 +1,49 @@ +"""The homee siren platform.""" + +from typing import Any + +from pyHomee.const import AttributeType + +from homeassistant.components.siren import SirenEntity, SirenEntityFeature +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback + +from . import HomeeConfigEntry +from .entity import HomeeEntity + +PARALLEL_UPDATES = 0 + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: HomeeConfigEntry, + async_add_devices: AddConfigEntryEntitiesCallback, +) -> None: + """Add siren entities for homee.""" + + async_add_devices( + HomeeSiren(attribute, config_entry) + for node in config_entry.runtime_data.nodes + for attribute in node.attributes + if attribute.type == AttributeType.SIREN + ) + + +class HomeeSiren(HomeeEntity, SirenEntity): + """Representation of a homee siren device.""" + + _attr_name = None + _attr_supported_features = SirenEntityFeature.TURN_ON | SirenEntityFeature.TURN_OFF + + @property + def is_on(self) -> bool: + """Return the state of the siren.""" + return self._attribute.current_value == 1.0 + + async def async_turn_on(self, **kwargs: Any) -> None: + """Turn the siren on.""" + await self.async_set_homee_value(1) + + async def async_turn_off(self, **kwargs: Any) -> None: + """Turn the siren off.""" + await self.async_set_homee_value(0) diff --git a/tests/components/homee/fixtures/siren.json b/tests/components/homee/fixtures/siren.json new file mode 100644 index 00000000000..8a8ee9c877b --- /dev/null +++ b/tests/components/homee/fixtures/siren.json @@ -0,0 +1,52 @@ +{ + "id": 1, + "name": "Test Siren", + "profile": 4027, + "image": "default", + "favorite": 0, + "order": 2, + "protocol": 3, + "routing": 0, + "state": 1, + "state_changed": 1731094262, + "added": 1680027880, + "history": 1, + "cube_type": 3, + "note": "", + "services": 4, + "phonetic_name": "", + "owner": 2, + "security": 0, + "attributes": [ + { + "id": 1, + "node_id": 1, + "instance": 0, + "minimum": 0, + "maximum": 1, + "current_value": 0.0, + "target_value": 0.0, + "last_value": 0.0, + "unit": "n/a", + "step_value": 1.0, + "editable": 1, + "type": 13, + "state": 1, + "last_changed": 1736003985, + "changed_by": 1, + "changed_by_id": 0, + "based_on": 1, + "data": "", + "name": "", + "options": { + "automations": ["toggle"], + "history": { + "day": 35, + "week": 5, + "month": 1, + "stepped": true + } + } + } + ] +} diff --git a/tests/components/homee/snapshots/test_siren.ambr b/tests/components/homee/snapshots/test_siren.ambr new file mode 100644 index 00000000000..90f43834dc9 --- /dev/null +++ b/tests/components/homee/snapshots/test_siren.ambr @@ -0,0 +1,50 @@ +# serializer version: 1 +# name: test_siren_snapshot[siren.test_siren-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'siren', + 'entity_category': None, + 'entity_id': 'siren.test_siren', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': None, + 'platform': 'homee', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': , + 'translation_key': None, + 'unique_id': '00055511EECC-1-1', + 'unit_of_measurement': None, + }) +# --- +# name: test_siren_snapshot[siren.test_siren-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Test Siren', + 'supported_features': , + }), + 'context': , + 'entity_id': 'siren.test_siren', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'off', + }) +# --- diff --git a/tests/components/homee/test_siren.py b/tests/components/homee/test_siren.py new file mode 100644 index 00000000000..ccdc01a5f53 --- /dev/null +++ b/tests/components/homee/test_siren.py @@ -0,0 +1,86 @@ +"""Test homee sirens.""" + +from unittest.mock import MagicMock, patch + +import pytest +from syrupy.assertion import SnapshotAssertion + +from homeassistant.components.siren import ( + DOMAIN as SIREN_DOMAIN, + SERVICE_TOGGLE, + SERVICE_TURN_OFF, + SERVICE_TURN_ON, +) +from homeassistant.const import ATTR_ENTITY_ID, Platform +from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er + +from . import async_update_attribute_value, build_mock_node, setup_integration + +from tests.common import MockConfigEntry, snapshot_platform + + +async def setup_siren( + hass: HomeAssistant, mock_config_entry: MockConfigEntry, mock_homee: MagicMock +) -> None: + """Setups the integration siren tests.""" + mock_homee.nodes = [build_mock_node("siren.json")] + mock_homee.get_node_by_id.return_value = mock_homee.nodes[0] + await setup_integration(hass, mock_config_entry) + + +@pytest.mark.parametrize( + ("service", "target_value"), + [ + (SERVICE_TURN_ON, 1), + (SERVICE_TURN_OFF, 0), + (SERVICE_TOGGLE, 1), + ], +) +async def test_siren_services( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_homee: MagicMock, + service: str, + target_value: int, +) -> None: + """Test siren services.""" + await setup_siren(hass, mock_config_entry, mock_homee) + + await hass.services.async_call( + SIREN_DOMAIN, + service, + {ATTR_ENTITY_ID: "siren.test_siren"}, + ) + mock_homee.set_value.assert_called_once_with(1, 1, target_value) + + +async def test_siren_state( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_homee: MagicMock, +) -> None: + """Test siren state.""" + await setup_siren(hass, mock_config_entry, mock_homee) + + state = hass.states.get("siren.test_siren") + assert state.state == "off" + + attribute = mock_homee.nodes[0].attributes[0] + await async_update_attribute_value(hass, attribute, 1.0) + state = hass.states.get("siren.test_siren") + assert state.state == "on" + + +async def test_siren_snapshot( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_homee: MagicMock, + entity_registry: er.EntityRegistry, + snapshot: SnapshotAssertion, +) -> None: + """Test siren snapshot.""" + with patch("homeassistant.components.homee.PLATFORMS", [Platform.SIREN]): + await setup_siren(hass, mock_config_entry, mock_homee) + + await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)