From dbc38cdc6b3bb3a149f72bf45c7568ab4dbde1cc Mon Sep 17 00:00:00 2001 From: Sid <27780930+autinerd@users.noreply.github.com> Date: Wed, 30 Apr 2025 20:42:00 +0200 Subject: [PATCH] Add switch platform to eheimdigital (#142412) * Add switch platform to eheimdigital * docstring fixes * Review * Review --------- Co-authored-by: Joostlek --- .../components/eheimdigital/__init__.py | 1 + .../components/eheimdigital/icons.json | 8 ++ .../components/eheimdigital/switch.py | 70 ++++++++++++ .../eheimdigital/snapshots/test_switch.ambr | 48 ++++++++ tests/components/eheimdigital/test_switch.py | 105 ++++++++++++++++++ 5 files changed, 232 insertions(+) create mode 100644 homeassistant/components/eheimdigital/switch.py create mode 100644 tests/components/eheimdigital/snapshots/test_switch.ambr create mode 100644 tests/components/eheimdigital/test_switch.py diff --git a/homeassistant/components/eheimdigital/__init__.py b/homeassistant/components/eheimdigital/__init__.py index fee2db089b2..881396ea4af 100644 --- a/homeassistant/components/eheimdigital/__init__.py +++ b/homeassistant/components/eheimdigital/__init__.py @@ -14,6 +14,7 @@ PLATFORMS = [ Platform.LIGHT, Platform.NUMBER, Platform.SENSOR, + Platform.SWITCH, Platform.TIME, ] diff --git a/homeassistant/components/eheimdigital/icons.json b/homeassistant/components/eheimdigital/icons.json index a09e15e008c..41a362c757c 100644 --- a/homeassistant/components/eheimdigital/icons.json +++ b/homeassistant/components/eheimdigital/icons.json @@ -31,6 +31,14 @@ } } }, + "switch": { + "filter_active": { + "default": "mdi:pump", + "state": { + "off": "mdi:pump-off" + } + } + }, "time": { "day_start_time": { "default": "mdi:weather-sunny" diff --git a/homeassistant/components/eheimdigital/switch.py b/homeassistant/components/eheimdigital/switch.py new file mode 100644 index 00000000000..de23feff322 --- /dev/null +++ b/homeassistant/components/eheimdigital/switch.py @@ -0,0 +1,70 @@ +"""EHEIM Digital switches.""" + +from typing import Any, override + +from eheimdigital.classic_vario import EheimDigitalClassicVario +from eheimdigital.device import EheimDigitalDevice + +from homeassistant.components.switch import SwitchEntity +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback + +from .coordinator import EheimDigitalConfigEntry, EheimDigitalUpdateCoordinator +from .entity import EheimDigitalEntity + +# Coordinator is used to centralize the data updates +PARALLEL_UPDATES = 0 + + +async def async_setup_entry( + hass: HomeAssistant, + entry: EheimDigitalConfigEntry, + async_add_entities: AddConfigEntryEntitiesCallback, +) -> None: + """Set up the callbacks for the coordinator so switches can be added as devices are found.""" + coordinator = entry.runtime_data + + def async_setup_device_entities( + device_address: dict[str, EheimDigitalDevice], + ) -> None: + """Set up the switch entities for one or multiple devices.""" + entities: list[SwitchEntity] = [] + for device in device_address.values(): + if isinstance(device, EheimDigitalClassicVario): + entities.append(EheimDigitalClassicVarioSwitch(coordinator, device)) # noqa: PERF401 + + async_add_entities(entities) + + coordinator.add_platform_callback(async_setup_device_entities) + async_setup_device_entities(coordinator.hub.devices) + + +class EheimDigitalClassicVarioSwitch( + EheimDigitalEntity[EheimDigitalClassicVario], SwitchEntity +): + """Represent an EHEIM Digital classicVARIO switch entity.""" + + _attr_translation_key = "filter_active" + _attr_name = None + + def __init__( + self, + coordinator: EheimDigitalUpdateCoordinator, + device: EheimDigitalClassicVario, + ) -> None: + """Initialize an EHEIM Digital classicVARIO switch entity.""" + super().__init__(coordinator, device) + self._attr_unique_id = device.mac_address + self._async_update_attrs() + + @override + async def async_turn_off(self, **kwargs: Any) -> None: + await self._device.set_active(active=False) + + @override + async def async_turn_on(self, **kwargs: Any) -> None: + await self._device.set_active(active=True) + + @override + def _async_update_attrs(self) -> None: + self._attr_is_on = self._device.is_active diff --git a/tests/components/eheimdigital/snapshots/test_switch.ambr b/tests/components/eheimdigital/snapshots/test_switch.ambr new file mode 100644 index 00000000000..73d229cb4ba --- /dev/null +++ b/tests/components/eheimdigital/snapshots/test_switch.ambr @@ -0,0 +1,48 @@ +# serializer version: 1 +# name: test_setup_classic_vario[switch.mock_classicvario-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.mock_classicvario', + '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': 'eheimdigital', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'filter_active', + 'unique_id': '00:00:00:00:00:03', + 'unit_of_measurement': None, + }) +# --- +# name: test_setup_classic_vario[switch.mock_classicvario-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Mock classicVARIO', + }), + 'context': , + 'entity_id': 'switch.mock_classicvario', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'on', + }) +# --- diff --git a/tests/components/eheimdigital/test_switch.py b/tests/components/eheimdigital/test_switch.py new file mode 100644 index 00000000000..440e4776b37 --- /dev/null +++ b/tests/components/eheimdigital/test_switch.py @@ -0,0 +1,105 @@ +"""Tests for the switch module.""" + +from unittest.mock import AsyncMock, MagicMock, patch + +from eheimdigital.types import EheimDeviceType +import pytest +from syrupy.assertion import SnapshotAssertion + +from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN +from homeassistant.const import ( + ATTR_ENTITY_ID, + SERVICE_TURN_OFF, + SERVICE_TURN_ON, + STATE_OFF, + STATE_ON, + Platform, +) +from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er + +from .conftest import init_integration + +from tests.common import MockConfigEntry, snapshot_platform + + +@pytest.mark.usefixtures("classic_vario_mock") +async def test_setup_classic_vario( + hass: HomeAssistant, + eheimdigital_hub_mock: MagicMock, + mock_config_entry: MockConfigEntry, + entity_registry: er.EntityRegistry, + snapshot: SnapshotAssertion, +) -> None: + """Test switch platform setup for the filter.""" + mock_config_entry.add_to_hass(hass) + + with ( + patch("homeassistant.components.eheimdigital.PLATFORMS", [Platform.SWITCH]), + patch( + "homeassistant.components.eheimdigital.coordinator.asyncio.Event", + new=AsyncMock, + ), + ): + await hass.config_entries.async_setup(mock_config_entry.entry_id) + + await eheimdigital_hub_mock.call_args.kwargs["device_found_callback"]( + "00:00:00:00:00:03", EheimDeviceType.VERSION_EHEIM_CLASSIC_VARIO + ) + await hass.async_block_till_done() + + await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id) + + +@pytest.mark.parametrize( + ("service", "active"), [(SERVICE_TURN_OFF, False), (SERVICE_TURN_ON, True)] +) +async def test_turn_on_off( + hass: HomeAssistant, + eheimdigital_hub_mock: MagicMock, + mock_config_entry: MockConfigEntry, + classic_vario_mock: MagicMock, + service: str, + active: bool, +) -> None: + """Test turning on/off the switch.""" + await init_integration(hass, mock_config_entry) + + await eheimdigital_hub_mock.call_args.kwargs["device_found_callback"]( + "00:00:00:00:00:03", EheimDeviceType.VERSION_EHEIM_CLASSIC_VARIO + ) + await hass.async_block_till_done() + + await hass.services.async_call( + SWITCH_DOMAIN, + service, + {ATTR_ENTITY_ID: "switch.mock_classicvario"}, + blocking=True, + ) + + classic_vario_mock.set_active.assert_awaited_once_with(active=active) + + +async def test_state_update( + hass: HomeAssistant, + eheimdigital_hub_mock: MagicMock, + mock_config_entry: MockConfigEntry, + classic_vario_mock: MagicMock, +) -> None: + """Test the switch state update.""" + await init_integration(hass, mock_config_entry) + + await eheimdigital_hub_mock.call_args.kwargs["device_found_callback"]( + "00:00:00:00:00:03", EheimDeviceType.VERSION_EHEIM_CLASSIC_VARIO + ) + await hass.async_block_till_done() + + assert (state := hass.states.get("switch.mock_classicvario")) + assert state.state == STATE_ON + + classic_vario_mock.is_active = False + + await eheimdigital_hub_mock.call_args.kwargs["receive_callback"]() + + assert (state := hass.states.get("switch.mock_classicvario")) + assert state.state == STATE_OFF