diff --git a/homeassistant/components/chacon_dio/__init__.py b/homeassistant/components/chacon_dio/__init__.py index 00558572fca..94617cb3929 100644 --- a/homeassistant/components/chacon_dio/__init__.py +++ b/homeassistant/components/chacon_dio/__init__.py @@ -17,7 +17,7 @@ from homeassistant.core import Event, HomeAssistant _LOGGER = logging.getLogger(__name__) -PLATFORMS: list[Platform] = [Platform.COVER] +PLATFORMS: list[Platform] = [Platform.COVER, Platform.SWITCH] @dataclass diff --git a/homeassistant/components/chacon_dio/switch.py b/homeassistant/components/chacon_dio/switch.py new file mode 100644 index 00000000000..be178c3c3b5 --- /dev/null +++ b/homeassistant/components/chacon_dio/switch.py @@ -0,0 +1,74 @@ +"""Switch Platform for Chacon Dio REV-LIGHT and switch plug devices.""" + +import logging +from typing import Any + +from dio_chacon_wifi_api.const import DeviceTypeEnum + +from homeassistant.components.switch import SwitchDeviceClass, SwitchEntity +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from . import ChaconDioConfigEntry +from .entity import ChaconDioEntity + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ChaconDioConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up Chacon Dio switch devices.""" + data = config_entry.runtime_data + client = data.client + + async_add_entities( + ChaconDioSwitch(client, device) + for device in data.list_devices + if device["type"] + in (DeviceTypeEnum.SWITCH_LIGHT.value, DeviceTypeEnum.SWITCH_PLUG.value) + ) + + +class ChaconDioSwitch(ChaconDioEntity, SwitchEntity): + """Object for controlling a Chacon Dio switch.""" + + _attr_device_class = SwitchDeviceClass.SWITCH + _attr_name = None + + def _update_attr(self, data: dict[str, Any]) -> None: + """Recomputes the attributes values either at init or when the device state changes.""" + self._attr_available = data["connected"] + self._attr_is_on = data["is_on"] + + async def async_turn_on(self, **kwargs: Any) -> None: + """Turn on the switch. + + Turned on status is effective after the server callback that triggers callback_device_state. + """ + + _LOGGER.debug( + "Turn on the switch %s , %s, %s", + self.target_id, + self.entity_id, + self._attr_is_on, + ) + + await self.client.switch_switch(self.target_id, True) + + async def async_turn_off(self, **kwargs: Any) -> None: + """Turn off the switch. + + Turned on status is effective after the server callback that triggers callback_device_state. + """ + + _LOGGER.debug( + "Turn off the switch %s , %s, %s", + self.target_id, + self.entity_id, + self._attr_is_on, + ) + + await self.client.switch_switch(self.target_id, False) diff --git a/tests/components/chacon_dio/conftest.py b/tests/components/chacon_dio/conftest.py index 3c3b970cec0..186bc468bee 100644 --- a/tests/components/chacon_dio/conftest.py +++ b/tests/components/chacon_dio/conftest.py @@ -65,6 +65,8 @@ def mock_dio_chacon_client() -> Generator[AsyncMock]: client.get_user_id.return_value = "dummy-user-id" client.search_all_devices.return_value = MOCK_COVER_DEVICE + client.switch_switch.return_value = {} + client.move_shutter_direction.return_value = {} client.disconnect.return_value = {} diff --git a/tests/components/chacon_dio/snapshots/test_switch.ambr b/tests/components/chacon_dio/snapshots/test_switch.ambr new file mode 100644 index 00000000000..7a65dad5445 --- /dev/null +++ b/tests/components/chacon_dio/snapshots/test_switch.ambr @@ -0,0 +1,48 @@ +# serializer version: 1 +# name: test_entities[switch.switch_mock_1-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'switch', + 'entity_category': None, + 'entity_id': 'switch.switch_mock_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': None, + 'platform': 'chacon_dio', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': 'L4HActuator_idmock1', + 'unit_of_measurement': None, + }) +# --- +# name: test_entities[switch.switch_mock_1-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'switch', + 'friendly_name': 'Switch mock 1', + }), + 'context': , + 'entity_id': 'switch.switch_mock_1', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'on', + }) +# --- diff --git a/tests/components/chacon_dio/test_switch.py b/tests/components/chacon_dio/test_switch.py new file mode 100644 index 00000000000..a5ad0d0ea13 --- /dev/null +++ b/tests/components/chacon_dio/test_switch.py @@ -0,0 +1,132 @@ +"""Test the Chacon Dio switch.""" + +from collections.abc import Callable +from unittest.mock import AsyncMock + +from syrupy.assertion import SnapshotAssertion + +from homeassistant.components.switch import ( + DOMAIN as SWITCH_DOMAIN, + SERVICE_TURN_OFF, + SERVICE_TURN_ON, +) +from homeassistant.const import ATTR_ENTITY_ID, STATE_OFF, STATE_ON +from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er + +from . import setup_integration + +from tests.common import MockConfigEntry, snapshot_platform + +SWITCH_ENTITY_ID = "switch.switch_mock_1" + +MOCK_SWITCH_DEVICE = { + "L4HActuator_idmock1": { + "id": "L4HActuator_idmock1", + "name": "Switch mock 1", + "type": "SWITCH_LIGHT", + "model": "CERNwd-3B_1.0.6", + "connected": True, + "is_on": True, + } +} + + +async def test_entities( + hass: HomeAssistant, + mock_dio_chacon_client: AsyncMock, + mock_config_entry: MockConfigEntry, + entity_registry: er.EntityRegistry, + snapshot: SnapshotAssertion, +) -> None: + """Test the creation and values of the Chacon Dio switches.""" + + mock_dio_chacon_client.search_all_devices.return_value = MOCK_SWITCH_DEVICE + + await setup_integration(hass, mock_config_entry) + + await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id) + + +async def test_switch_actions( + hass: HomeAssistant, + mock_dio_chacon_client: AsyncMock, + mock_config_entry: MockConfigEntry, + entity_registry: er.EntityRegistry, + snapshot: SnapshotAssertion, +) -> None: + """Test the actions on the Chacon Dio switch.""" + + mock_dio_chacon_client.search_all_devices.return_value = MOCK_SWITCH_DEVICE + + await setup_integration(hass, mock_config_entry) + + await hass.services.async_call( + SWITCH_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: SWITCH_ENTITY_ID}, + blocking=True, + ) + state = hass.states.get(SWITCH_ENTITY_ID) + assert state.state == STATE_ON + + await hass.services.async_call( + SWITCH_DOMAIN, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: SWITCH_ENTITY_ID}, + blocking=True, + ) + state = hass.states.get(SWITCH_ENTITY_ID) + # turn off does not change directly the state, it is made by a server side callback. + assert state.state == STATE_ON + + +async def test_switch_callbacks( + hass: HomeAssistant, + mock_dio_chacon_client: AsyncMock, + mock_config_entry: MockConfigEntry, + entity_registry: er.EntityRegistry, +) -> None: + """Test the callbacks on the Chacon Dio switches.""" + + mock_dio_chacon_client.search_all_devices.return_value = MOCK_SWITCH_DEVICE + + await setup_integration(hass, mock_config_entry) + + # Server side callback tests + # We find the callback method on the mock client + callback_device_state_function: Callable = ( + mock_dio_chacon_client.set_callback_device_state_by_device.call_args[0][1] + ) + + # Define a method to simply call it + async def _callback_device_state_function(is_on: bool) -> None: + callback_device_state_function( + { + "id": "L4HActuator_idmock1", + "connected": True, + "is_on": is_on, + } + ) + await hass.async_block_till_done() + + # And call it to effectively launch the callback as the server would do + await _callback_device_state_function(False) + state = hass.states.get(SWITCH_ENTITY_ID) + assert state + assert state.state == STATE_OFF + + +async def test_no_switch_found( + hass: HomeAssistant, + mock_dio_chacon_client: AsyncMock, + mock_config_entry: MockConfigEntry, + entity_registry: er.EntityRegistry, +) -> None: + """Test the switch absence.""" + + mock_dio_chacon_client.search_all_devices.return_value = None + + await setup_integration(hass, mock_config_entry) + + assert not hass.states.async_entity_ids(SWITCH_DOMAIN)