diff --git a/homeassistant/components/palazzetti/__init__.py b/homeassistant/components/palazzetti/__init__.py index f20b3d11261..dbf1baa0c28 100644 --- a/homeassistant/components/palazzetti/__init__.py +++ b/homeassistant/components/palazzetti/__init__.py @@ -7,7 +7,12 @@ from homeassistant.core import HomeAssistant from .coordinator import PalazzettiConfigEntry, PalazzettiDataUpdateCoordinator -PLATFORMS: list[Platform] = [Platform.CLIMATE, Platform.NUMBER, Platform.SENSOR] +PLATFORMS: list[Platform] = [ + Platform.BUTTON, + Platform.CLIMATE, + Platform.NUMBER, + Platform.SENSOR, +] async def async_setup_entry(hass: HomeAssistant, entry: PalazzettiConfigEntry) -> bool: diff --git a/homeassistant/components/palazzetti/button.py b/homeassistant/components/palazzetti/button.py new file mode 100644 index 00000000000..cd4765576ed --- /dev/null +++ b/homeassistant/components/palazzetti/button.py @@ -0,0 +1,52 @@ +"""Support for Palazzetti buttons.""" + +from __future__ import annotations + +from pypalazzetti.exceptions import CommunicationError + +from homeassistant.components.button import ButtonEntity +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from . import PalazzettiConfigEntry +from .const import DOMAIN +from .coordinator import PalazzettiDataUpdateCoordinator +from .entity import PalazzettiEntity + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: PalazzettiConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up Palazzetti button platform.""" + + coordinator = config_entry.runtime_data + if coordinator.client.has_fan_silent: + async_add_entities([PalazzettiSilentButtonEntity(coordinator)]) + + +class PalazzettiSilentButtonEntity(PalazzettiEntity, ButtonEntity): + """Representation of a Palazzetti Silent button.""" + + _attr_translation_key = "silent" + + def __init__( + self, + coordinator: PalazzettiDataUpdateCoordinator, + ) -> None: + """Initialize a Palazzetti Silent button.""" + super().__init__(coordinator) + self._attr_unique_id = f"{coordinator.config_entry.unique_id}-silent" + + async def async_press(self) -> None: + """Press the button.""" + try: + await self.coordinator.client.set_fan_silent() + except CommunicationError as err: + raise HomeAssistantError( + translation_domain=DOMAIN, translation_key="cannot_connect" + ) from err + + await self.coordinator.async_request_refresh() diff --git a/homeassistant/components/palazzetti/climate.py b/homeassistant/components/palazzetti/climate.py index 356f3a7306f..301ede422d6 100644 --- a/homeassistant/components/palazzetti/climate.py +++ b/homeassistant/components/palazzetti/climate.py @@ -16,7 +16,7 @@ from homeassistant.exceptions import HomeAssistantError, ServiceValidationError from homeassistant.helpers.entity_platform import AddEntitiesCallback from . import PalazzettiConfigEntry -from .const import DOMAIN, FAN_AUTO, FAN_HIGH, FAN_MODES, FAN_SILENT +from .const import DOMAIN, FAN_AUTO, FAN_HIGH, FAN_MODES from .coordinator import PalazzettiDataUpdateCoordinator from .entity import PalazzettiEntity @@ -57,8 +57,6 @@ class PalazzettiClimateEntity(PalazzettiEntity, ClimateEntity): self._attr_fan_modes = list( map(str, range(client.fan_speed_min, client.fan_speed_max + 1)) ) - if client.has_fan_silent: - self._attr_fan_modes.insert(0, FAN_SILENT) if client.has_fan_high: self._attr_fan_modes.append(FAN_HIGH) if client.has_fan_auto: @@ -130,9 +128,7 @@ class PalazzettiClimateEntity(PalazzettiEntity, ClimateEntity): async def async_set_fan_mode(self, fan_mode: str) -> None: """Set new fan mode.""" try: - if fan_mode == FAN_SILENT: - await self.coordinator.client.set_fan_silent() - elif fan_mode == FAN_HIGH: + if fan_mode == FAN_HIGH: await self.coordinator.client.set_fan_high() elif fan_mode == FAN_AUTO: await self.coordinator.client.set_fan_auto() diff --git a/homeassistant/components/palazzetti/const.py b/homeassistant/components/palazzetti/const.py index b2e27b2a6fd..1b68cf99f9d 100644 --- a/homeassistant/components/palazzetti/const.py +++ b/homeassistant/components/palazzetti/const.py @@ -18,7 +18,7 @@ ERROR_CANNOT_CONNECT = "cannot_connect" FAN_SILENT: Final = "silent" FAN_HIGH: Final = "high" FAN_AUTO: Final = "auto" -FAN_MODES: Final = [FAN_SILENT, "1", "2", "3", "4", "5", FAN_HIGH, FAN_AUTO] +FAN_MODES: Final = ["0", "1", "2", "3", "4", "5", FAN_HIGH, FAN_AUTO] STATUS_TO_HA: Final[dict[StateType, str]] = { 0: "off", diff --git a/homeassistant/components/palazzetti/icons.json b/homeassistant/components/palazzetti/icons.json new file mode 100644 index 00000000000..c20a9572618 --- /dev/null +++ b/homeassistant/components/palazzetti/icons.json @@ -0,0 +1,9 @@ +{ + "entity": { + "button": { + "silent": { + "default": "mdi:volume-mute" + } + } + } +} diff --git a/homeassistant/components/palazzetti/quality_scale.yaml b/homeassistant/components/palazzetti/quality_scale.yaml index c1c83be235c..5d57313bfde 100644 --- a/homeassistant/components/palazzetti/quality_scale.yaml +++ b/homeassistant/components/palazzetti/quality_scale.yaml @@ -67,9 +67,7 @@ rules: entity-translations: done exception-translations: done icon-translations: - status: exempt - comment: | - This integration does not have custom icons. + status: done reconfiguration-flow: todo repair-issues: status: exempt diff --git a/homeassistant/components/palazzetti/strings.json b/homeassistant/components/palazzetti/strings.json index ad7bc498bd1..93233a9b1e4 100644 --- a/homeassistant/components/palazzetti/strings.json +++ b/homeassistant/components/palazzetti/strings.json @@ -38,6 +38,11 @@ } }, "entity": { + "button": { + "silent": { + "name": "Silent" + } + }, "climate": { "palazzetti": { "state_attributes": { diff --git a/tests/components/palazzetti/snapshots/test_button.ambr b/tests/components/palazzetti/snapshots/test_button.ambr new file mode 100644 index 00000000000..6827c9a1f22 --- /dev/null +++ b/tests/components/palazzetti/snapshots/test_button.ambr @@ -0,0 +1,47 @@ +# serializer version: 1 +# name: test_all_entities[button.stove_silent-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'button', + 'entity_category': None, + 'entity_id': 'button.stove_silent', + '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': 'Silent', + 'platform': 'palazzetti', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'silent', + 'unique_id': '11:22:33:44:55:66-silent', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[button.stove_silent-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Stove Silent', + }), + 'context': , + 'entity_id': 'button.stove_silent', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- diff --git a/tests/components/palazzetti/snapshots/test_climate.ambr b/tests/components/palazzetti/snapshots/test_climate.ambr index e7cea3749a1..aa637039df9 100644 --- a/tests/components/palazzetti/snapshots/test_climate.ambr +++ b/tests/components/palazzetti/snapshots/test_climate.ambr @@ -6,7 +6,6 @@ 'area_id': None, 'capabilities': dict({ 'fan_modes': list([ - 'silent', '1', '2', '3', @@ -56,7 +55,6 @@ 'current_temperature': 18, 'fan_mode': '3', 'fan_modes': list([ - 'silent', '1', '2', '3', diff --git a/tests/components/palazzetti/test_button.py b/tests/components/palazzetti/test_button.py new file mode 100644 index 00000000000..de0f26fe8aa --- /dev/null +++ b/tests/components/palazzetti/test_button.py @@ -0,0 +1,69 @@ +"""Tests for the Palazzetti button platform.""" + +from unittest.mock import AsyncMock, patch + +from pypalazzetti.exceptions import CommunicationError +import pytest +from syrupy import SnapshotAssertion + +from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN, SERVICE_PRESS +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 setup_integration + +from tests.common import MockConfigEntry, snapshot_platform + +ENTITY_ID = "button.stove_silent" + + +async def test_all_entities( + hass: HomeAssistant, + snapshot: SnapshotAssertion, + mock_palazzetti_client: AsyncMock, + mock_config_entry: MockConfigEntry, + entity_registry: er.EntityRegistry, +) -> None: + """Test all entities.""" + with patch("homeassistant.components.palazzetti.PLATFORMS", [Platform.BUTTON]): + await setup_integration(hass, mock_config_entry) + + await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id) + + +async def test_async_press( + hass: HomeAssistant, + mock_palazzetti_client: AsyncMock, + mock_config_entry: MockConfigEntry, +) -> None: + """Test pressing via service call.""" + await setup_integration(hass, mock_config_entry) + + await hass.services.async_call( + BUTTON_DOMAIN, + SERVICE_PRESS, + {ATTR_ENTITY_ID: ENTITY_ID}, + blocking=True, + ) + mock_palazzetti_client.set_fan_silent.assert_called_once() + + +async def test_async_press_error( + hass: HomeAssistant, + mock_palazzetti_client: AsyncMock, + mock_config_entry: MockConfigEntry, +) -> None: + """Test pressing with error via service call.""" + await setup_integration(hass, mock_config_entry) + + mock_palazzetti_client.set_fan_silent.side_effect = CommunicationError() + error_message = "Could not connect to the device" + with pytest.raises(HomeAssistantError, match=error_message): + await hass.services.async_call( + BUTTON_DOMAIN, + SERVICE_PRESS, + {ATTR_ENTITY_ID: ENTITY_ID}, + blocking=True, + ) diff --git a/tests/components/palazzetti/test_climate.py b/tests/components/palazzetti/test_climate.py index 78af8f00bdb..22bd04f234e 100644 --- a/tests/components/palazzetti/test_climate.py +++ b/tests/components/palazzetti/test_climate.py @@ -15,7 +15,7 @@ from homeassistant.components.climate import ( SERVICE_SET_TEMPERATURE, HVACMode, ) -from homeassistant.components.palazzetti.const import FAN_AUTO, FAN_HIGH, FAN_SILENT +from homeassistant.components.palazzetti.const import FAN_AUTO, FAN_HIGH from homeassistant.const import ATTR_ENTITY_ID, ATTR_TEMPERATURE, Platform from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError, ServiceValidationError @@ -118,15 +118,6 @@ async def test_async_set_data( ) # Set Fan Mode: Success - await hass.services.async_call( - CLIMATE_DOMAIN, - SERVICE_SET_FAN_MODE, - {ATTR_ENTITY_ID: ENTITY_ID, ATTR_FAN_MODE: FAN_SILENT}, - blocking=True, - ) - mock_palazzetti_client.set_fan_silent.assert_called_once() - mock_palazzetti_client.set_fan_silent.reset_mock() - await hass.services.async_call( CLIMATE_DOMAIN, SERVICE_SET_FAN_MODE, diff --git a/tests/components/palazzetti/test_number.py b/tests/components/palazzetti/test_number.py index 939c7c72c19..54318ed5c74 100644 --- a/tests/components/palazzetti/test_number.py +++ b/tests/components/palazzetti/test_number.py @@ -49,7 +49,7 @@ async def test_async_set_data( blocking=True, ) mock_palazzetti_client.set_power_mode.assert_called_once_with(1) - mock_palazzetti_client.set_on.reset_mock() + mock_palazzetti_client.set_power_mode.reset_mock() # Set value: Error mock_palazzetti_client.set_power_mode.side_effect = CommunicationError() @@ -60,7 +60,7 @@ async def test_async_set_data( {ATTR_ENTITY_ID: ENTITY_ID, "value": 1}, blocking=True, ) - mock_palazzetti_client.set_on.reset_mock() + mock_palazzetti_client.set_power_mode.reset_mock() mock_palazzetti_client.set_power_mode.side_effect = ValidationError() with pytest.raises(ServiceValidationError):