diff --git a/homeassistant/components/homee/__init__.py b/homeassistant/components/homee/__init__.py index 9837d6094ff..7d9db9eb180 100644 --- a/homeassistant/components/homee/__init__.py +++ b/homeassistant/components/homee/__init__.py @@ -14,7 +14,7 @@ from .const import DOMAIN _LOGGER = logging.getLogger(__name__) -PLATFORMS = [Platform.COVER, Platform.SENSOR] +PLATFORMS = [Platform.COVER, Platform.SENSOR, Platform.SWITCH] type HomeeConfigEntry = ConfigEntry[Homee] diff --git a/homeassistant/components/homee/const.py b/homeassistant/components/homee/const.py index 1d7ce27335f..54d7773890f 100644 --- a/homeassistant/components/homee/const.py +++ b/homeassistant/components/homee/const.py @@ -1,5 +1,7 @@ """Constants for the homee integration.""" +from pyHomee.const import NodeProfile + from homeassistant.const import ( DEGREE, LIGHT_LUX, @@ -62,3 +64,33 @@ WINDOW_MAP = { 2.0: "tilted", } WINDOW_MAP_REVERSED = {0.0: "open", 1.0: "closed", 2.0: "tilted"} + +# Profile Groups +CLIMATE_PROFILES = [ + NodeProfile.COSI_THERM_CHANNEL, + NodeProfile.HEATING_SYSTEM, + NodeProfile.RADIATOR_THERMOSTAT, + NodeProfile.ROOM_THERMOSTAT, + NodeProfile.ROOM_THERMOSTAT_WITH_HUMIDITY_SENSOR, + NodeProfile.THERMOSTAT_WITH_HEATING_AND_COOLING, + NodeProfile.WIFI_RADIATOR_THERMOSTAT, + NodeProfile.WIFI_ROOM_THERMOSTAT, +] +LIGHT_PROFILES = [ + NodeProfile.DIMMABLE_COLOR_LIGHT, + NodeProfile.DIMMABLE_COLOR_METERING_PLUG, + NodeProfile.DIMMABLE_COLOR_TEMPERATURE_LIGHT, + NodeProfile.DIMMABLE_EXTENDED_COLOR_LIGHT, + NodeProfile.DIMMABLE_LIGHT, + NodeProfile.DIMMABLE_LIGHT_WITH_BRIGHTNESS_SENSOR, + NodeProfile.DIMMABLE_LIGHT_WITH_BRIGHTNESS_AND_PRESENCE_SENSOR, + NodeProfile.DIMMABLE_LIGHT_WITH_PRESENCE_SENSOR, + NodeProfile.DIMMABLE_METERING_SWITCH, + NodeProfile.DIMMABLE_METERING_PLUG, + NodeProfile.DIMMABLE_PLUG, + NodeProfile.DIMMABLE_RGBWLIGHT, + NodeProfile.DIMMABLE_SWITCH, + NodeProfile.WIFI_DIMMABLE_RGBWLIGHT, + NodeProfile.WIFI_DIMMABLE_LIGHT, + NodeProfile.WIFI_ON_OFF_DIMMABLE_METERING_SWITCH, +] diff --git a/homeassistant/components/homee/entity.py b/homeassistant/components/homee/entity.py index 5a46b366d3e..5a7f34b1c37 100644 --- a/homeassistant/components/homee/entity.py +++ b/homeassistant/components/homee/entity.py @@ -26,10 +26,14 @@ class HomeeEntity(Entity): f"{entry.runtime_data.settings.uid}-{attribute.node_id}-{attribute.id}" ) self._entry = entry + node = entry.runtime_data.get_node_by_id(attribute.node_id) self._attr_device_info = DeviceInfo( identifiers={ (DOMAIN, f"{entry.runtime_data.settings.uid}-{attribute.node_id}") - } + }, + name=node.name, + model=get_name_for_enum(NodeProfile, node.profile), + via_device=(DOMAIN, entry.runtime_data.settings.uid), ) self._host_connected = entry.runtime_data.connected @@ -50,6 +54,17 @@ class HomeeEntity(Entity): """Return the availability of the underlying node.""" return (self._attribute.state == AttributeState.NORMAL) and self._host_connected + async def async_set_value(self, value: float) -> None: + """Set an attribute value on the homee node.""" + homee = self._entry.runtime_data + try: + await homee.set_value(self._attribute.node_id, self._attribute.id, value) + except ConnectionClosed as exception: + raise HomeAssistantError( + translation_domain=DOMAIN, + translation_key="connection_closed", + ) from exception + async def async_update(self) -> None: """Update entity from homee.""" homee = self._entry.runtime_data @@ -129,13 +144,6 @@ class HomeeNodeEntity(Entity): return None - def has_attribute(self, attribute_type: AttributeType) -> bool: - """Check if an attribute of the given type exists.""" - if self._node.attribute_map is None: - return False - - return attribute_type in self._node.attribute_map - async def async_set_value(self, attribute: HomeeAttribute, value: float) -> None: """Set an attribute value on the homee node.""" homee = self._entry.runtime_data diff --git a/homeassistant/components/homee/icons.json b/homeassistant/components/homee/icons.json index 3b1ee17b89c..07ae598095b 100644 --- a/homeassistant/components/homee/icons.json +++ b/homeassistant/components/homee/icons.json @@ -7,6 +7,14 @@ "window_position": { "default": "mdi:window-closed" } + }, + "switch": { + "watchdog_on_off": { + "default": "mdi:dog" + }, + "manual_operation": { + "default": "mdi:hand-back-left" + } } } } diff --git a/homeassistant/components/homee/strings.json b/homeassistant/components/homee/strings.json index 025d8df21d6..07f8eb6fb04 100644 --- a/homeassistant/components/homee/strings.json +++ b/homeassistant/components/homee/strings.json @@ -151,11 +151,25 @@ "tilted": "Tilted" } } + }, + "switch": { + "external_binary_input": { + "name": "Child lock" + }, + "manual_operation": { + "name": "Manual operation" + }, + "on_off_instance": { + "name": "Switch {instance}" + }, + "watchdog": { + "name": "Watchdog" + } } }, "exceptions": { "connection_closed": { - "message": "Could not connect to Homee while setting attribute" + "message": "Could not connect to Homee while setting attribute." } } } diff --git a/homeassistant/components/homee/switch.py b/homeassistant/components/homee/switch.py new file mode 100644 index 00000000000..e8b87b2b8e0 --- /dev/null +++ b/homeassistant/components/homee/switch.py @@ -0,0 +1,127 @@ +"""The homee switch platform.""" + +from collections.abc import Callable +from dataclasses import dataclass +from typing import Any + +from pyHomee.const import AttributeType, NodeProfile +from pyHomee.model import HomeeAttribute + +from homeassistant.components.switch import ( + SwitchDeviceClass, + SwitchEntity, + SwitchEntityDescription, +) +from homeassistant.const import EntityCategory +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback + +from . import HomeeConfigEntry +from .const import CLIMATE_PROFILES, LIGHT_PROFILES +from .entity import HomeeEntity + + +def get_device_class( + attribute: HomeeAttribute, config_entry: HomeeConfigEntry +) -> SwitchDeviceClass: + """Check device class of Switch according to node profile.""" + node = config_entry.runtime_data.get_node_by_id(attribute.node_id) + if node.profile in [ + NodeProfile.ON_OFF_PLUG, + NodeProfile.METERING_PLUG, + NodeProfile.DOUBLE_ON_OFF_PLUG, + NodeProfile.IMPULSE_PLUG, + ]: + return SwitchDeviceClass.OUTLET + + return SwitchDeviceClass.SWITCH + + +@dataclass(frozen=True, kw_only=True) +class HomeeSwitchEntityDescription(SwitchEntityDescription): + """A class that describes Homee switch entity.""" + + device_class_fn: Callable[[HomeeAttribute, HomeeConfigEntry], SwitchDeviceClass] = ( + lambda attribute, entry: SwitchDeviceClass.SWITCH + ) + + +SWITCH_DESCRIPTIONS: dict[AttributeType, HomeeSwitchEntityDescription] = { + AttributeType.EXTERNAL_BINARY_INPUT: HomeeSwitchEntityDescription( + key="external_binary_input", entity_category=EntityCategory.CONFIG + ), + AttributeType.MANUAL_OPERATION: HomeeSwitchEntityDescription( + key="manual_operation" + ), + AttributeType.ON_OFF: HomeeSwitchEntityDescription( + key="on_off", device_class_fn=get_device_class, name=None + ), + AttributeType.WATCHDOG_ON_OFF: HomeeSwitchEntityDescription( + key="watchdog", entity_category=EntityCategory.CONFIG + ), +} + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: HomeeConfigEntry, + async_add_devices: AddConfigEntryEntitiesCallback, +) -> None: + """Set up the switch platform for the Homee component.""" + + for node in config_entry.runtime_data.nodes: + async_add_devices( + HomeeSwitch(attribute, config_entry, SWITCH_DESCRIPTIONS[attribute.type]) + for attribute in node.attributes + if (attribute.type in SWITCH_DESCRIPTIONS and attribute.editable) + and not ( + attribute.type == AttributeType.ON_OFF + and node.profile in LIGHT_PROFILES + ) + and not ( + attribute.type == AttributeType.MANUAL_OPERATION + and node.profile in CLIMATE_PROFILES + ) + ) + + +class HomeeSwitch(HomeeEntity, SwitchEntity): + """Representation of a Homee switch.""" + + entity_description: HomeeSwitchEntityDescription + + def __init__( + self, + attribute: HomeeAttribute, + entry: HomeeConfigEntry, + description: HomeeSwitchEntityDescription, + ) -> None: + """Initialize a Homee switch entity.""" + super().__init__(attribute, entry) + self.entity_description = description + if attribute.instance == 0: + if attribute.type == AttributeType.ON_OFF: + self._attr_name = None + else: + self._attr_translation_key = description.key + else: + self._attr_translation_key = f"{description.key}_instance" + self._attr_translation_placeholders = {"instance": str(attribute.instance)} + + @property + def is_on(self) -> bool: + """Return True if entity is on.""" + return bool(self._attribute.current_value) + + @property + def device_class(self) -> SwitchDeviceClass: + """Return the device class of the switch.""" + return self.entity_description.device_class_fn(self._attribute, self._entry) + + async def async_turn_on(self, **kwargs: Any) -> None: + """Turn the switch on.""" + await self.async_set_value(1) + + async def async_turn_off(self, **kwargs: Any) -> None: + """Turn the switch off.""" + await self.async_set_value(0) diff --git a/tests/components/homee/fixtures/switch_single.json b/tests/components/homee/fixtures/switch_single.json new file mode 100644 index 00000000000..74b7fae048d --- /dev/null +++ b/tests/components/homee/fixtures/switch_single.json @@ -0,0 +1,74 @@ +{ + "id": 2, + "name": "Test Switch Single", + "profile": 15, + "image": "nodeicon_bulb", + "favorite": 0, + "order": 27, + "protocol": 3, + "routing": 0, + "state": 1, + "state_changed": 1736188706, + "added": 1610308228, + "history": 1, + "cube_type": 3, + "note": "", + "services": 7, + "phonetic_name": "", + "owner": 2, + "security": 0, + "attributes": [ + { + "id": 1, + "node_id": 2, + "instance": 0, + "minimum": 0, + "maximum": 1, + "current_value": 1.0, + "target_value": 0.0, + "last_value": 0.0, + "unit": "n/a", + "step_value": 1.0, + "editable": 1, + "type": 1, + "state": 1, + "last_changed": 1736743294, + "changed_by": 1, + "changed_by_id": 0, + "based_on": 1, + "data": "", + "name": "", + "options": { + "can_observe": [300], + "automations": ["toggle"], + "history": { + "day": 35, + "week": 5, + "month": 1, + "stepped": true + } + } + }, + { + "id": 2, + "node_id": 2, + "instance": 0, + "minimum": 0, + "maximum": 1, + "current_value": 1.0, + "target_value": 1.0, + "last_value": 0.0, + "unit": "", + "step_value": 1.0, + "editable": 1, + "type": 385, + "state": 1, + "last_changed": 1735663169, + "changed_by": 1, + "changed_by_id": 0, + "based_on": 0, + "data": "", + "name": "" + } + ] +} diff --git a/tests/components/homee/fixtures/switches.json b/tests/components/homee/fixtures/switches.json new file mode 100644 index 00000000000..333717591a7 --- /dev/null +++ b/tests/components/homee/fixtures/switches.json @@ -0,0 +1,127 @@ +{ + "id": 1, + "name": "Test Switch", + "profile": 10, + "image": "nodeicon_dimmablebulb", + "favorite": 0, + "order": 27, + "protocol": 3, + "routing": 0, + "state": 1, + "state_changed": 1736188706, + "added": 1610308228, + "history": 1, + "cube_type": 3, + "note": "All known switches", + "services": 7, + "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": 309, + "state": 1, + "last_changed": 1677692134, + "changed_by": 1, + "changed_by_id": 0, + "based_on": 1, + "data": "", + "name": "" + }, + { + "id": 2, + "node_id": 1, + "instance": 0, + "minimum": 0, + "maximum": 1, + "current_value": 0.0, + "target_value": 0.0, + "last_value": 0.0, + "unit": "", + "step_value": 1.0, + "editable": 1, + "type": 91, + "state": 1, + "last_changed": 1711796633, + "changed_by": 1, + "changed_by_id": 0, + "based_on": 1, + "data": "", + "name": "" + }, + { + "id": 3, + "node_id": 1, + "instance": 1, + "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": 1, + "state": 1, + "last_changed": 1736743294, + "changed_by": 1, + "changed_by_id": 0, + "based_on": 1, + "data": "", + "name": "" + }, + { + "id": 4, + "node_id": 1, + "instance": 2, + "minimum": 0, + "maximum": 1, + "current_value": 1.0, + "target_value": 1.0, + "last_value": 1.0, + "unit": "n/a", + "step_value": 1.0, + "editable": 1, + "type": 1, + "state": 1, + "last_changed": 1736743294, + "changed_by": 1, + "changed_by_id": 0, + "based_on": 1, + "data": "", + "name": "" + }, + { + "id": 5, + "node_id": 1, + "instance": 0, + "minimum": 0, + "maximum": 1, + "current_value": 1.0, + "target_value": 1.0, + "last_value": 0.0, + "unit": "n/a", + "step_value": 1.0, + "editable": 1, + "type": 385, + "state": 1, + "last_changed": 1735663169, + "changed_by": 1, + "changed_by_id": 0, + "based_on": 0, + "data": "", + "name": "" + } + ] +} diff --git a/tests/components/homee/snapshots/test_switch.ambr b/tests/components/homee/snapshots/test_switch.ambr new file mode 100644 index 00000000000..43c1773cede --- /dev/null +++ b/tests/components/homee/snapshots/test_switch.ambr @@ -0,0 +1,241 @@ +# serializer version: 1 +# name: test_switch_snapshot[switch.test_switch_child_lock-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'switch', + 'entity_category': , + 'entity_id': 'switch.test_switch_child_lock', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Child lock', + 'platform': 'homee', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'external_binary_input', + 'unique_id': '00055511EECC-1-1', + 'unit_of_measurement': None, + }) +# --- +# name: test_switch_snapshot[switch.test_switch_child_lock-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'switch', + 'friendly_name': 'Test Switch Child lock', + }), + 'context': , + 'entity_id': 'switch.test_switch_child_lock', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'off', + }) +# --- +# name: test_switch_snapshot[switch.test_switch_manual_operation-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'switch', + 'entity_category': None, + 'entity_id': 'switch.test_switch_manual_operation', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Manual operation', + 'platform': 'homee', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'manual_operation', + 'unique_id': '00055511EECC-1-2', + 'unit_of_measurement': None, + }) +# --- +# name: test_switch_snapshot[switch.test_switch_manual_operation-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'switch', + 'friendly_name': 'Test Switch Manual operation', + }), + 'context': , + 'entity_id': 'switch.test_switch_manual_operation', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'off', + }) +# --- +# name: test_switch_snapshot[switch.test_switch_switch_1-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'switch', + 'entity_category': None, + 'entity_id': 'switch.test_switch_switch_1', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Switch 1', + 'platform': 'homee', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'on_off_instance', + 'unique_id': '00055511EECC-1-3', + 'unit_of_measurement': None, + }) +# --- +# name: test_switch_snapshot[switch.test_switch_switch_1-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'outlet', + 'friendly_name': 'Test Switch Switch 1', + }), + 'context': , + 'entity_id': 'switch.test_switch_switch_1', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'off', + }) +# --- +# name: test_switch_snapshot[switch.test_switch_switch_2-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'switch', + 'entity_category': None, + 'entity_id': 'switch.test_switch_switch_2', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Switch 2', + 'platform': 'homee', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'on_off_instance', + 'unique_id': '00055511EECC-1-4', + 'unit_of_measurement': None, + }) +# --- +# name: test_switch_snapshot[switch.test_switch_switch_2-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'outlet', + 'friendly_name': 'Test Switch Switch 2', + }), + 'context': , + 'entity_id': 'switch.test_switch_switch_2', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'on', + }) +# --- +# name: test_switch_snapshot[switch.test_switch_watchdog-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'switch', + 'entity_category': , + 'entity_id': 'switch.test_switch_watchdog', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Watchdog', + 'platform': 'homee', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'watchdog', + 'unique_id': '00055511EECC-1-5', + 'unit_of_measurement': None, + }) +# --- +# name: test_switch_snapshot[switch.test_switch_watchdog-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'switch', + 'friendly_name': 'Test Switch Watchdog', + }), + 'context': , + 'entity_id': 'switch.test_switch_watchdog', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'on', + }) +# --- diff --git a/tests/components/homee/test_sensor.py b/tests/components/homee/test_sensor.py index 8ee48d3ea97..0f66709c532 100644 --- a/tests/components/homee/test_sensor.py +++ b/tests/components/homee/test_sensor.py @@ -28,6 +28,7 @@ async def test_up_down_values( ) -> None: """Test values for up/down sensor.""" mock_homee.nodes = [build_mock_node("sensors.json")] + mock_homee.get_node_by_id.return_value = mock_homee.nodes[0] await setup_integration(hass, mock_config_entry) assert hass.states.get("sensor.test_multisensor_state").state == OPEN_CLOSE_MAP[0] @@ -56,6 +57,7 @@ async def test_window_position( ) -> None: """Test values for window handle position.""" mock_homee.nodes = [build_mock_node("sensors.json")] + mock_homee.get_node_by_id.return_value = mock_homee.nodes[0] await setup_integration(hass, mock_config_entry) assert ( @@ -88,6 +90,7 @@ async def test_brightness_sensor( ) -> None: """Test brightness sensor's lx & klx units and naming of multi-instance sensors.""" mock_homee.nodes = [build_mock_node("sensors.json")] + mock_homee.get_node_by_id.return_value = mock_homee.nodes[0] await setup_integration(hass, mock_config_entry) sensor_state = hass.states.get("sensor.test_multisensor_illuminance_1") @@ -112,6 +115,7 @@ async def test_sensor_snapshot( ) -> None: """Test the multisensor snapshot.""" mock_homee.nodes = [build_mock_node("sensors.json")] + mock_homee.get_node_by_id.return_value = mock_homee.nodes[0] await setup_integration(hass, mock_config_entry) entity_registry.async_update_entity( "sensor.test_multisensor_node_state", disabled_by=None diff --git a/tests/components/homee/test_switch.py b/tests/components/homee/test_switch.py new file mode 100644 index 00000000000..bb14313f487 --- /dev/null +++ b/tests/components/homee/test_switch.py @@ -0,0 +1,179 @@ +"""Test Homee switches.""" + +from unittest.mock import MagicMock, patch + +import pytest +from syrupy.assertion import SnapshotAssertion +from websockets import frames +from websockets.exceptions import ConnectionClosed + +from homeassistant.components.homee.const import DOMAIN +from homeassistant.components.switch import ( + DOMAIN as SWITCH_DOMAIN, + SERVICE_TURN_OFF, + SERVICE_TURN_ON, + STATE_ON, + SwitchDeviceClass, +) +from homeassistant.const import ATTR_ENTITY_ID, Platform +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers import entity_registry as er + +from . import build_mock_node, setup_integration + +from tests.common import MockConfigEntry, snapshot_platform + + +async def test_switch_state( + hass: HomeAssistant, + mock_homee: MagicMock, + mock_config_entry: MockConfigEntry, +) -> None: + """Test if the correct state is returned.""" + mock_homee.nodes = [build_mock_node("switches.json")] + mock_homee.get_node_by_id.return_value = mock_homee.nodes[0] + await setup_integration(hass, mock_config_entry) + + assert hass.states.get("switch.test_switch_switch_1").state is not STATE_ON + switch = mock_homee.nodes[0].attributes[2] + switch.current_value = 1 + switch.add_on_changed_listener.call_args_list[0][0][0](switch) + await hass.async_block_till_done() + assert hass.states.get("switch.test_switch_switch_1").state is STATE_ON + + +async def test_switch_turn_on( + hass: HomeAssistant, + mock_homee: MagicMock, + mock_config_entry: MockConfigEntry, +) -> None: + """Test turn-on service.""" + mock_homee.nodes = [build_mock_node("switches.json")] + mock_homee.get_node_by_id.return_value = mock_homee.nodes[0] + await setup_integration(hass, mock_config_entry) + + assert hass.states.get("switch.test_switch_switch_1").state is not STATE_ON + await hass.services.async_call( + SWITCH_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: "switch.test_switch_switch_1"}, + blocking=True, + ) + + mock_homee.set_value.assert_called_once_with(1, 3, 1) + + +async def test_switch_turn_off( + hass: HomeAssistant, + mock_homee: MagicMock, + mock_config_entry: MockConfigEntry, +) -> None: + """Test turn-off service.""" + mock_homee.nodes = [build_mock_node("switches.json")] + mock_homee.get_node_by_id.return_value = mock_homee.nodes[0] + await setup_integration(hass, mock_config_entry) + + assert hass.states.get("switch.test_switch_watchdog").state is STATE_ON + await hass.services.async_call( + SWITCH_DOMAIN, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: "switch.test_switch_watchdog"}, + blocking=True, + ) + mock_homee.set_value.assert_called_once_with(1, 5, 0) + + +async def test_switch_device_class( + hass: HomeAssistant, + mock_homee: MagicMock, + mock_config_entry: MockConfigEntry, +) -> None: + """Test if device class gets set correctly.""" + mock_homee.nodes = [build_mock_node("switches.json")] + mock_homee.get_node_by_id.return_value = mock_homee.nodes[0] + await setup_integration(hass, mock_config_entry) + + assert ( + hass.states.get("switch.test_switch_switch_1").attributes["device_class"] + == SwitchDeviceClass.OUTLET + ) + assert ( + hass.states.get("switch.test_switch_watchdog").attributes["device_class"] + == SwitchDeviceClass.SWITCH + ) + + +async def test_switch_no_name( + hass: HomeAssistant, + mock_homee: MagicMock, + mock_config_entry: MockConfigEntry, +) -> None: + """Test switch gets no name when it is the main feature of the device.""" + mock_homee.nodes = [build_mock_node("switch_single.json")] + mock_homee.nodes[0].profile = 2002 + mock_homee.get_node_by_id.return_value = mock_homee.nodes[0] + await setup_integration(hass, mock_config_entry) + + assert ( + hass.states.get("switch.test_switch_single").attributes["friendly_name"] + == "Test Switch Single" + ) + + +async def test_switch_device_class_no_outlet( + hass: HomeAssistant, + mock_homee: MagicMock, + mock_config_entry: MockConfigEntry, +) -> None: + """Test if on_off device class gets set correctly if node-profile is not a plug.""" + mock_homee.nodes = [build_mock_node("switches.json")] + mock_homee.nodes[0].profile = 2002 + mock_homee.get_node_by_id.return_value = mock_homee.nodes[0] + await setup_integration(hass, mock_config_entry) + + assert ( + hass.states.get("switch.test_switch_switch_1").attributes["device_class"] + == SwitchDeviceClass.SWITCH + ) + + +async def test_send_error( + hass: HomeAssistant, + mock_homee: MagicMock, + mock_config_entry: MockConfigEntry, +) -> None: + """Test failed set_value command.""" + mock_homee.nodes = [build_mock_node("switches.json")] + mock_homee.get_node_by_id.return_value = mock_homee.nodes[0] + await setup_integration(hass, mock_config_entry) + + mock_homee.set_value.side_effect = ConnectionClosed( + rcvd=frames.Close(1002, "Protocol Error"), sent=None + ) + with pytest.raises(HomeAssistantError) as exc_info: + await hass.services.async_call( + SWITCH_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: "switch.test_switch_switch_1"}, + blocking=True, + ) + + assert exc_info.value.translation_domain == DOMAIN + assert exc_info.value.translation_key == "connection_closed" + + +async def test_switch_snapshot( + hass: HomeAssistant, + mock_homee: MagicMock, + mock_config_entry: MockConfigEntry, + entity_registry: er.EntityRegistry, + snapshot: SnapshotAssertion, +) -> None: + """Test the multisensor snapshot.""" + mock_homee.nodes = [build_mock_node("switches.json")] + mock_homee.get_node_by_id.return_value = mock_homee.nodes[0] + with patch("homeassistant.components.homee.PLATFORMS", [Platform.SWITCH]): + await setup_integration(hass, mock_config_entry) + + await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)