From a7fb69cf3ecc3aa937245f0fbb667aeea9358e49 Mon Sep 17 00:00:00 2001 From: Michael <35783820+mib1185@users.noreply.github.com> Date: Mon, 22 Jun 2026 18:06:45 +0200 Subject: [PATCH] Add fan speed mode control to Synology DSM (#173051) Co-authored-by: Joostlek --- .../components/synology_dsm/common.py | 27 ++++ .../components/synology_dsm/const.py | 3 + .../components/synology_dsm/diagnostics.py | 5 + .../components/synology_dsm/icons.json | 10 ++ .../components/synology_dsm/select.py | 77 +++++++++++ .../components/synology_dsm/strings.json | 10 ++ tests/components/synology_dsm/common.py | 6 + tests/components/synology_dsm/conftest.py | 3 +- .../snapshots/test_diagnostics.ambr | 1 + .../synology_dsm/snapshots/test_select.ambr | 63 +++++++++ tests/components/synology_dsm/test_backup.py | 4 +- .../synology_dsm/test_config_flow.py | 7 +- .../synology_dsm/test_diagnostics.py | 3 +- .../synology_dsm/test_media_source.py | 3 +- tests/components/synology_dsm/test_repairs.py | 3 +- tests/components/synology_dsm/test_select.py | 129 ++++++++++++++++++ tests/components/synology_dsm/test_sensor.py | 3 + 17 files changed, 351 insertions(+), 6 deletions(-) create mode 100644 homeassistant/components/synology_dsm/select.py create mode 100644 tests/components/synology_dsm/snapshots/test_select.ambr create mode 100644 tests/components/synology_dsm/test_select.py diff --git a/homeassistant/components/synology_dsm/common.py b/homeassistant/components/synology_dsm/common.py index 3e9710f36fce..eb0d2317ded8 100644 --- a/homeassistant/components/synology_dsm/common.py +++ b/homeassistant/components/synology_dsm/common.py @@ -8,6 +8,7 @@ import logging from awesomeversion import AwesomeVersion from synology_dsm import SynologyDSM from synology_dsm.api.core.external_usb import SynoCoreExternalUSB +from synology_dsm.api.core.hardware import SynoCoreHardware from synology_dsm.api.core.security import SynoCoreSecurity from synology_dsm.api.core.system import SynoCoreSystem from synology_dsm.api.core.upgrade import SynoCoreUpgrade @@ -68,6 +69,7 @@ class SynoApi: # DSM APIs self.file_station: SynoFileStation | None = None + self.hardware: SynoCoreHardware | None = None self.information: SynoDSMInformation | None = None self.network: SynoDSMNetwork | None = None self.photos: SynoPhotos | None = None @@ -82,6 +84,7 @@ class SynoApi: # Should we fetch them self._fetching_entities: dict[str, set[str]] = {} self._with_file_station = True + self._with_hardware = True self._with_information = True self._with_photos = True self._with_security = True @@ -205,6 +208,14 @@ class SynoApi: self._with_file_station, ) + # check if hardware info is available + try: + await self.dsm.hardware.update() + except SYNOLOGY_CONNECTION_EXCEPTIONS as ex: + self._with_hardware = False + self.dsm.reset(SynoCoreHardware.API_KEY) + LOGGER.debug("Disabled fetching hardware data during setup: %s", ex) + await self._fetch_device_configuration() try: @@ -265,6 +276,9 @@ class SynoApi: self._with_external_usb = bool( self._fetching_entities.get(SynoCoreExternalUSB.API_KEY) ) + self._with_hardware = bool( + self._fetching_entities.get(SynoCoreHardware.API_KEY) + ) # Reset not used API, information is not reset since it's used in device_info if not self._with_security: @@ -335,6 +349,15 @@ class SynoApi: self.dsm.reset(self.external_usb) self.external_usb = None + if not self._with_hardware: + LOGGER.debug( + "Disable hardware api from being updated for '%s'", + self._entry.unique_id, + ) + if self.hardware: + self.dsm.reset(self.hardware) + self.hardware = None + async def _fetch_device_configuration(self) -> None: """Fetch initial device config.""" self.network = self.dsm.network @@ -385,6 +408,10 @@ class SynoApi: ) self.external_usb = self.dsm.external_usb + if self._with_hardware: + LOGGER.debug("Enable hardware api updates for '%s'", self._entry.unique_id) + self.hardware = self.dsm.hardware + async def _syno_api_executer(self, api_call: Callable) -> None: """Synology api call wrapper.""" try: diff --git a/homeassistant/components/synology_dsm/const.py b/homeassistant/components/synology_dsm/const.py index 2a38f377d527..ecaf4de32a9f 100644 --- a/homeassistant/components/synology_dsm/const.py +++ b/homeassistant/components/synology_dsm/const.py @@ -6,6 +6,7 @@ from aiohttp import ClientTimeout from synology_dsm.api.surveillance_station.const import SNAPSHOT_PROFILE_BALANCED from synology_dsm.exceptions import ( SynologyDSMAPIErrorException, + SynologyDSMAPINoDataException, SynologyDSMLogin2SARequiredException, SynologyDSMLoginDisabledAccountException, SynologyDSMLoginFailedException, @@ -26,6 +27,7 @@ PLATFORMS = [ Platform.BINARY_SENSOR, Platform.BUTTON, Platform.CAMERA, + Platform.SELECT, Platform.SENSOR, Platform.SWITCH, Platform.UPDATE, @@ -76,6 +78,7 @@ SYNOLOGY_AUTH_FAILED_EXCEPTIONS = ( SYNOLOGY_CONNECTION_EXCEPTIONS = ( SynologyDSMAPIErrorException, + SynologyDSMAPINoDataException, SynologyDSMLoginFailedException, SynologyDSMRequestException, ) diff --git a/homeassistant/components/synology_dsm/diagnostics.py b/homeassistant/components/synology_dsm/diagnostics.py index fbec12d78770..db0cc91dd38c 100644 --- a/homeassistant/components/synology_dsm/diagnostics.py +++ b/homeassistant/components/synology_dsm/diagnostics.py @@ -63,6 +63,11 @@ async def async_get_config_entry_diagnostics( "size_total": partition.partition_size_total(False), } + if syno_api.hardware is not None: + diag_data["device_info"]["fan_speed_mode"] = ( + syno_api.dsm.hardware.fan_speed.value + ) + if syno_api.network is not None: for intf in syno_api.network.interfaces: diag_data["network"]["interfaces"][intf["id"]] = { diff --git a/homeassistant/components/synology_dsm/icons.json b/homeassistant/components/synology_dsm/icons.json index 44f699043653..5c52862eb5af 100644 --- a/homeassistant/components/synology_dsm/icons.json +++ b/homeassistant/components/synology_dsm/icons.json @@ -5,6 +5,16 @@ "default": "mdi:power" } }, + "select": { + "fan_speed_mode": { + "default": "mdi:fan", + "state": { + "cool": "mdi:fan-speed-2", + "full_speed": "mdi:fan-speed-3", + "quiet": "mdi:fan-speed-1" + } + } + }, "sensor": { "cpu_15min_load": { "default": "mdi:chip" diff --git a/homeassistant/components/synology_dsm/select.py b/homeassistant/components/synology_dsm/select.py new file mode 100644 index 000000000000..bb4cd994f49b --- /dev/null +++ b/homeassistant/components/synology_dsm/select.py @@ -0,0 +1,77 @@ +"""Support for Synology DSM select entities.""" + +from dataclasses import dataclass +from typing import override + +from synology_dsm.api.core.hardware import FanSpeed, SynoCoreHardware + +from homeassistant.components.select import SelectEntity, SelectEntityDescription +from homeassistant.const import EntityCategory +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback + +from . import SynoApi +from .coordinator import SynologyDSMCentralUpdateCoordinator, SynologyDSMConfigEntry +from .entity import SynologyDSMBaseEntity, SynologyDSMEntityDescription + +FAN_SPEED_MAP = { + FanSpeed.QUIET: "quiet", + FanSpeed.COOL: "cool", + FanSpeed.FULL: "full_speed", +} +FAN_SPEED_MAP_INVERSE = {v: k for k, v in FAN_SPEED_MAP.items()} + + +@dataclass(frozen=True, kw_only=True) +class SynologyDSMSelectEntityDescription( + SynologyDSMEntityDescription, SelectEntityDescription +): + """Describes Synology DSM select entity.""" + + +async def async_setup_entry( + hass: HomeAssistant, + entry: SynologyDSMConfigEntry, + async_add_entities: AddConfigEntryEntitiesCallback, +) -> None: + """Set select entities for device.""" + data = entry.runtime_data + if data.api.hardware is not None: + async_add_entities( + [SynologyDSMFanSpeedMode(data.api, data.coordinator_central)] + ) + + +class SynologyDSMFanSpeedMode( + SynologyDSMBaseEntity[SynologyDSMCentralUpdateCoordinator], SelectEntity +): + """Represent a Synology DSM fan speed mode select entity.""" + + _attr_options = list(FAN_SPEED_MAP.values()) + entity_description: SynologyDSMSelectEntityDescription + + def __init__( + self, + api: SynoApi, + coordinator: SynologyDSMCentralUpdateCoordinator, + ) -> None: + """Initialize a Synology DSM select entity.""" + description = SynologyDSMSelectEntityDescription( + api_key=SynoCoreHardware.API_KEY, + key="fan_speed_mode", + translation_key="fan_speed_mode", + entity_category=EntityCategory.CONFIG, + ) + super().__init__(api, coordinator, description) + + @property + def current_option(self) -> str | None: + """Return the selected entity option to represent the entity state.""" + return FAN_SPEED_MAP[self._api.dsm.hardware.fan_speed] + + @override + async def async_select_option(self, option: str) -> None: + """Set the fan speed mode.""" + await self._api.dsm.hardware.set_fan_speed(FAN_SPEED_MAP_INVERSE[option]) + await self._api.dsm.hardware.update() + self.async_write_ha_state() diff --git a/homeassistant/components/synology_dsm/strings.json b/homeassistant/components/synology_dsm/strings.json index fe4c056398d4..d89eb4f66f23 100644 --- a/homeassistant/components/synology_dsm/strings.json +++ b/homeassistant/components/synology_dsm/strings.json @@ -81,6 +81,16 @@ "name": "Shut down" } }, + "select": { + "fan_speed_mode": { + "name": "Fan speed mode", + "state": { + "cool": "Cool mode", + "full_speed": "Full-speed mode", + "quiet": "Quiet mode" + } + } + }, "sensor": { "cpu_15min_load": { "name": "CPU load average (15 min)" diff --git a/tests/components/synology_dsm/common.py b/tests/components/synology_dsm/common.py index c4d99a83e187..717029b2271e 100644 --- a/tests/components/synology_dsm/common.py +++ b/tests/components/synology_dsm/common.py @@ -4,11 +4,17 @@ from unittest.mock import AsyncMock, Mock from awesomeversion import AwesomeVersion from synology_dsm.api.core.external_usb import SynoCoreExternalUSBDevice +from synology_dsm.api.core.hardware import FanSpeed from synology_dsm.api.storage.storage import SynoStorageDisk, SynoStorageVolume from .consts import SERIAL +def mock_dsm_hardware(fan_speed: FanSpeed = FanSpeed.COOL) -> Mock: + """Mock SynologyDSM hardware information.""" + return AsyncMock(update=AsyncMock(), fan_speed=fan_speed) + + def mock_dsm_information( serial: str | None = SERIAL, update_result: bool = True, diff --git a/tests/components/synology_dsm/conftest.py b/tests/components/synology_dsm/conftest.py index 96d6453cf169..908a0119a77e 100644 --- a/tests/components/synology_dsm/conftest.py +++ b/tests/components/synology_dsm/conftest.py @@ -8,7 +8,7 @@ import pytest from homeassistant.core import HomeAssistant from homeassistant.setup import async_setup_component -from .common import mock_dsm_information +from .common import mock_dsm_hardware, mock_dsm_information @pytest.fixture @@ -35,6 +35,7 @@ def fixture_dsm(): dsm.information = mock_dsm_information() dsm.network.update = AsyncMock(return_value=True) + dsm.hardware = mock_dsm_hardware() dsm.surveillance_station.update = AsyncMock(return_value=True) dsm.upgrade.update = AsyncMock(return_value=True) dsm.file = AsyncMock(get_shared_folders=AsyncMock(return_value=None)) diff --git a/tests/components/synology_dsm/snapshots/test_diagnostics.ambr b/tests/components/synology_dsm/snapshots/test_diagnostics.ambr index cd8b1be42b22..1cf0068c2915 100644 --- a/tests/components/synology_dsm/snapshots/test_diagnostics.ambr +++ b/tests/components/synology_dsm/snapshots/test_diagnostics.ambr @@ -2,6 +2,7 @@ # name: test_entry_diagnostics dict({ 'device_info': dict({ + 'fan_speed_mode': 'coolfan', 'model': 'DS1821+', 'ram': 32768, 'temperature': 58, diff --git a/tests/components/synology_dsm/snapshots/test_select.ambr b/tests/components/synology_dsm/snapshots/test_select.ambr new file mode 100644 index 000000000000..55830164d5bc --- /dev/null +++ b/tests/components/synology_dsm/snapshots/test_select.ambr @@ -0,0 +1,63 @@ +# serializer version: 1 +# name: test_fan_speed[select.nas_meontheinternet_com_fan_speed_mode-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': dict({ + 'options': list([ + 'quiet', + 'cool', + 'full_speed', + ]), + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'select', + 'entity_category': , + 'entity_id': 'select.nas_meontheinternet_com_fan_speed_mode', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Fan speed mode', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Fan speed mode', + 'platform': 'synology_dsm', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'fan_speed_mode', + 'unique_id': 'mySerial_SYNO.Core.Hardware:fan_speed_mode', + 'unit_of_measurement': None, + }) +# --- +# name: test_fan_speed[select.nas_meontheinternet_com_fan_speed_mode-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by Synology', + 'friendly_name': 'nas.meontheinternet.com Fan speed mode', + 'options': list([ + 'quiet', + 'cool', + 'full_speed', + ]), + }), + 'context': , + 'entity_id': 'select.nas_meontheinternet_com_fan_speed_mode', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'cool', + }) +# --- diff --git a/tests/components/synology_dsm/test_backup.py b/tests/components/synology_dsm/test_backup.py index cb155c2ccf47..3600667b4113 100644 --- a/tests/components/synology_dsm/test_backup.py +++ b/tests/components/synology_dsm/test_backup.py @@ -35,7 +35,7 @@ from homeassistant.core import HomeAssistant from homeassistant.setup import async_setup_component from homeassistant.util.aiohttp import MockStreamReader, MockStreamReaderChunked -from .common import mock_dsm_information +from .common import mock_dsm_hardware, mock_dsm_information from .consts import HOST, MACS, PASSWORD, PORT, USE_SSL, USERNAME from tests.common import MockConfigEntry @@ -93,6 +93,7 @@ def mock_dsm_with_filestation(): dsm.upgrade.update = AsyncMock(return_value=True) dsm.utilisation = Mock(cpu_user_load=1, update=AsyncMock(return_value=True)) dsm.network = Mock(update=AsyncMock(return_value=True), macs=MACS) + dsm.hardware = mock_dsm_hardware() dsm.storage = Mock( disks_ids=["sda", "sdb", "sdc"], volumes_ids=["volume_1"], @@ -146,6 +147,7 @@ def mock_dsm_without_filestation(): dsm.upgrade.update = AsyncMock(return_value=True) dsm.utilisation = Mock(cpu_user_load=1, update=AsyncMock(return_value=True)) dsm.network = Mock(update=AsyncMock(return_value=True), macs=MACS) + dsm.hardware = mock_dsm_hardware() dsm.information = mock_dsm_information() dsm.storage = Mock( disks_ids=["sda", "sdb", "sdc"], diff --git a/tests/components/synology_dsm/test_config_flow.py b/tests/components/synology_dsm/test_config_flow.py index 0fca5e723724..c9dec99fbcec 100644 --- a/tests/components/synology_dsm/test_config_flow.py +++ b/tests/components/synology_dsm/test_config_flow.py @@ -40,7 +40,7 @@ from homeassistant.helpers.service_info.ssdp import ( ) from homeassistant.helpers.service_info.zeroconf import ZeroconfServiceInfo -from .common import mock_dsm_information +from .common import mock_dsm_hardware, mock_dsm_information from .consts import ( DEVICE_TOKEN, HOST, @@ -68,6 +68,7 @@ def mock_controller_service(): dsm.upgrade.update = AsyncMock(return_value=True) dsm.utilisation = Mock(cpu_user_load=1, update=AsyncMock(return_value=True)) dsm.network = Mock(update=AsyncMock(return_value=True), macs=MACS) + dsm.hardware = mock_dsm_hardware() dsm.storage = Mock( disks_ids=["sda", "sdb", "sdc"], volumes_ids=["volume_1"], @@ -91,6 +92,7 @@ def mock_controller_service_2sa(): dsm.upgrade.update = AsyncMock(return_value=True) dsm.utilisation = Mock(cpu_user_load=1, update=AsyncMock(return_value=True)) dsm.network = Mock(update=AsyncMock(return_value=True), macs=MACS) + dsm.hardware = mock_dsm_hardware() dsm.storage = Mock( disks_ids=["sda", "sdb", "sdc"], volumes_ids=["volume_1"], @@ -112,6 +114,7 @@ def mock_controller_service_vdsm(): dsm.upgrade.update = AsyncMock(return_value=True) dsm.utilisation = Mock(cpu_user_load=1, update=AsyncMock(return_value=True)) dsm.network = Mock(update=AsyncMock(return_value=True), macs=MACS) + dsm.hardware = mock_dsm_hardware() dsm.storage = Mock( disks_ids=[], volumes_ids=["volume_1"], @@ -133,6 +136,7 @@ def mock_controller_service_with_filestation(): dsm.upgrade.update = AsyncMock(return_value=True) dsm.utilisation = Mock(cpu_user_load=1, update=AsyncMock(return_value=True)) dsm.network = Mock(update=AsyncMock(return_value=True), macs=MACS) + dsm.hardware = mock_dsm_hardware() dsm.storage = Mock( disks_ids=["sda", "sdb", "sdc"], volumes_ids=["volume_1"], @@ -166,6 +170,7 @@ def mock_controller_service_failed(): dsm.upgrade.update = AsyncMock(return_value=True) dsm.utilisation = Mock(cpu_user_load=None, update=AsyncMock(return_value=True)) dsm.network = Mock(update=AsyncMock(return_value=True), macs=[]) + dsm.hardware = mock_dsm_hardware() dsm.storage = Mock( disks_ids=[], volumes_ids=[], diff --git a/tests/components/synology_dsm/test_diagnostics.py b/tests/components/synology_dsm/test_diagnostics.py index f2bb35f488d8..aac70a516c11 100644 --- a/tests/components/synology_dsm/test_diagnostics.py +++ b/tests/components/synology_dsm/test_diagnostics.py @@ -19,7 +19,7 @@ from homeassistant.const import ( ) from homeassistant.core import HomeAssistant -from .common import mock_dsm_information +from .common import mock_dsm_hardware, mock_dsm_information from .consts import HOST, MACS, PASSWORD, PORT, SERIAL, USE_SSL, USERNAME from tests.common import MockConfigEntry @@ -92,6 +92,7 @@ def mock_dsm_with_usb(): ) ], ) + dsm.hardware = mock_dsm_hardware() dsm.information = mock_dsm_information() dsm.file = Mock(get_shared_folders=AsyncMock(return_value=None)) dsm.external_usb = Mock( diff --git a/tests/components/synology_dsm/test_media_source.py b/tests/components/synology_dsm/test_media_source.py index bb2fcf18c12e..f16a2bac18fb 100644 --- a/tests/components/synology_dsm/test_media_source.py +++ b/tests/components/synology_dsm/test_media_source.py @@ -28,7 +28,7 @@ from homeassistant.const import ( from homeassistant.core import HomeAssistant from homeassistant.util.aiohttp import MockRequest -from .common import mock_dsm_information +from .common import mock_dsm_hardware, mock_dsm_information from .consts import HOST, MACS, PASSWORD, PORT, USE_SSL, USERNAME from tests.common import MockConfigEntry @@ -42,6 +42,7 @@ def dsm_with_photos() -> MagicMock: dsm.update = AsyncMock(return_value=True) dsm.information = mock_dsm_information() dsm.network.update = AsyncMock(return_value=True) + dsm.hardware = mock_dsm_hardware() dsm.surveillance_station.update = AsyncMock(return_value=True) dsm.upgrade.update = AsyncMock(return_value=True) diff --git a/tests/components/synology_dsm/test_repairs.py b/tests/components/synology_dsm/test_repairs.py index de564aa9a6a9..964f31a20ef2 100644 --- a/tests/components/synology_dsm/test_repairs.py +++ b/tests/components/synology_dsm/test_repairs.py @@ -23,7 +23,7 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers import issue_registry as ir from homeassistant.setup import async_setup_component -from .common import mock_dsm_information +from .common import mock_dsm_hardware, mock_dsm_information from .consts import HOST, MACS, PASSWORD, PORT, USE_SSL, USERNAME from tests.common import ANY, MockConfigEntry @@ -42,6 +42,7 @@ def mock_dsm_with_filestation(): dsm.upgrade.update = AsyncMock(return_value=True) dsm.utilisation = Mock(cpu_user_load=1, update=AsyncMock(return_value=True)) dsm.network = Mock(update=AsyncMock(return_value=True), macs=MACS) + dsm.hardware = mock_dsm_hardware() dsm.storage = Mock( disks_ids=["sda", "sdb", "sdc"], volumes_ids=["volume_1"], diff --git a/tests/components/synology_dsm/test_select.py b/tests/components/synology_dsm/test_select.py new file mode 100644 index 000000000000..aa8b2fff3337 --- /dev/null +++ b/tests/components/synology_dsm/test_select.py @@ -0,0 +1,129 @@ +"""Tests for Synology DSM select entities.""" + +from unittest.mock import AsyncMock, MagicMock, Mock, patch + +from pylint_home_assistant.const import Platform +import pytest +from synology_dsm.api.core.hardware import FanSpeed +from syrupy.assertion import SnapshotAssertion + +from homeassistant.components.select import ( + DOMAIN as SELECT_DOMAIN, + SERVICE_SELECT_OPTION, +) +from homeassistant.components.synology_dsm.const import DOMAIN +from homeassistant.const import ( + ATTR_ENTITY_ID, + ATTR_OPTION, + CONF_HOST, + CONF_MAC, + CONF_PASSWORD, + CONF_PORT, + CONF_SSL, + CONF_USERNAME, +) +from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er + +from .common import mock_dsm_hardware, mock_dsm_information +from .consts import HOST, MACS, PASSWORD, PORT, SERIAL, USE_SSL, USERNAME + +from tests.common import MockConfigEntry, snapshot_platform + + +@pytest.fixture +def mock_dsm(): + """Mock a successful service.""" + with patch("homeassistant.components.synology_dsm.common.SynologyDSM") as dsm: + dsm.login = AsyncMock(return_value=True) + dsm.update = AsyncMock(return_value=True) + + dsm.surveillance_station.update = AsyncMock(return_value=True) + dsm.upgrade.update = AsyncMock(return_value=True) + dsm.network = Mock( + update=AsyncMock(return_value=True), macs=MACS, hostname=HOST + ) + dsm.hardware = mock_dsm_hardware() + dsm.information = mock_dsm_information() + dsm.file = Mock(get_shared_folders=AsyncMock(return_value=None)) + dsm.logout = AsyncMock(return_value=True) + yield dsm + + +async def test_fan_speed( + hass: HomeAssistant, + entity_registry: er.EntityRegistry, + snapshot: SnapshotAssertion, + mock_dsm: MagicMock, +) -> None: + """Test Synology DSM fan speed mode select entity.""" + with ( + patch( + "homeassistant.components.synology_dsm.common.SynologyDSM", + return_value=mock_dsm, + ), + patch("homeassistant.components.synology_dsm.PLATFORMS", ["select"]), + ): + entry = MockConfigEntry( + domain=DOMAIN, + data={ + CONF_HOST: HOST, + CONF_PORT: PORT, + CONF_SSL: USE_SSL, + CONF_USERNAME: USERNAME, + CONF_PASSWORD: PASSWORD, + CONF_MAC: MACS[0], + }, + unique_id=SERIAL, + ) + entry.add_to_hass(hass) + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + await snapshot_platform(hass, entity_registry, snapshot, entry.entry_id) + + +@pytest.mark.parametrize( + ("fan_speed", "fan_speed_parameter"), + [("full_speed", FanSpeed.FULL), ("cool", FanSpeed.COOL), ("quiet", FanSpeed.QUIET)], +) +async def test_fan_speed_select_option( + hass: HomeAssistant, + mock_dsm: MagicMock, + fan_speed: str, + fan_speed_parameter: FanSpeed, +) -> None: + """Test selecting a fan speed mode option.""" + with ( + patch( + "homeassistant.components.synology_dsm.common.SynologyDSM", + return_value=mock_dsm, + ), + patch("homeassistant.components.synology_dsm.PLATFORMS", [Platform.SELECT]), + ): + entry = MockConfigEntry( + domain=DOMAIN, + data={ + CONF_HOST: HOST, + CONF_PORT: PORT, + CONF_SSL: USE_SSL, + CONF_USERNAME: USERNAME, + CONF_PASSWORD: PASSWORD, + CONF_MAC: MACS[0], + }, + unique_id=SERIAL, + ) + entry.add_to_hass(hass) + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + await hass.services.async_call( + SELECT_DOMAIN, + SERVICE_SELECT_OPTION, + { + ATTR_ENTITY_ID: "select.nas_meontheinternet_com_fan_speed_mode", + ATTR_OPTION: fan_speed, + }, + blocking=True, + ) + assert mock_dsm.hardware.set_fan_speed.call_args[0][0] == fan_speed_parameter diff --git a/tests/components/synology_dsm/test_sensor.py b/tests/components/synology_dsm/test_sensor.py index 158059d3a364..b9cd8c290d73 100644 --- a/tests/components/synology_dsm/test_sensor.py +++ b/tests/components/synology_dsm/test_sensor.py @@ -24,6 +24,7 @@ from .common import ( mock_dsm_external_usb_devices_usb0, mock_dsm_external_usb_devices_usb1, mock_dsm_external_usb_devices_usb2, + mock_dsm_hardware, mock_dsm_information, mock_dsm_storage_get_disk, mock_dsm_storage_get_volume, @@ -49,6 +50,7 @@ def mock_dsm_with_usb(): dsm.network = Mock( update=AsyncMock(return_value=True), macs=MACS, hostname=HOST ) + dsm.hardware = mock_dsm_hardware() dsm.information = mock_dsm_information() dsm.storage = Mock( get_disk=mock_dsm_storage_get_disk, @@ -83,6 +85,7 @@ def mock_dsm_without_usb(): dsm.network = Mock( update=AsyncMock(return_value=True), macs=MACS, hostname=HOST ) + dsm.hardware = mock_dsm_hardware() dsm.information = mock_dsm_information() dsm.file = Mock(get_shared_folders=AsyncMock(return_value=None)) dsm.logout = AsyncMock(return_value=True)