diff --git a/homeassistant/components/plugwise/button.py b/homeassistant/components/plugwise/button.py new file mode 100644 index 00000000000..078d31bea12 --- /dev/null +++ b/homeassistant/components/plugwise/button.py @@ -0,0 +1,52 @@ +"""Plugwise Button component for Home Assistant.""" + +from __future__ import annotations + +from homeassistant.components.button import ButtonDeviceClass, ButtonEntity +from homeassistant.const import EntityCategory +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from . import PlugwiseConfigEntry +from .const import GATEWAY_ID, REBOOT +from .coordinator import PlugwiseDataUpdateCoordinator +from .entity import PlugwiseEntity +from .util import plugwise_command + + +async def async_setup_entry( + hass: HomeAssistant, + entry: PlugwiseConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up the Plugwise buttons from a ConfigEntry.""" + coordinator = entry.runtime_data + + gateway = coordinator.data.gateway + async_add_entities( + PlugwiseButtonEntity(coordinator, device_id) + for device_id in coordinator.data.devices + if device_id == gateway[GATEWAY_ID] and REBOOT in gateway + ) + + +class PlugwiseButtonEntity(PlugwiseEntity, ButtonEntity): + """Defines a Plugwise button.""" + + _attr_device_class = ButtonDeviceClass.RESTART + _attr_entity_category = EntityCategory.CONFIG + + def __init__( + self, + coordinator: PlugwiseDataUpdateCoordinator, + device_id: str, + ) -> None: + """Initialize the button.""" + super().__init__(coordinator, device_id) + self._attr_translation_key = REBOOT + self._attr_unique_id = f"{device_id}-reboot" + + @plugwise_command + async def async_press(self) -> None: + """Triggers the Plugwise button press service.""" + await self.coordinator.api.reboot_gateway() diff --git a/homeassistant/components/plugwise/const.py b/homeassistant/components/plugwise/const.py index 14599ce61fb..5e4dea5586b 100644 --- a/homeassistant/components/plugwise/const.py +++ b/homeassistant/components/plugwise/const.py @@ -17,14 +17,17 @@ FLOW_SMILE: Final = "smile (Adam/Anna/P1)" FLOW_STRETCH: Final = "stretch (Stretch)" FLOW_TYPE: Final = "flow_type" GATEWAY: Final = "gateway" +GATEWAY_ID: Final = "gateway_id" LOCATION: Final = "location" PW_TYPE: Final = "plugwise_type" +REBOOT: Final = "reboot" SMILE: Final = "smile" STRETCH: Final = "stretch" STRETCH_USERNAME: Final = "stretch" PLATFORMS: Final[list[str]] = [ Platform.BINARY_SENSOR, + Platform.BUTTON, Platform.CLIMATE, Platform.NUMBER, Platform.SELECT, diff --git a/homeassistant/components/plugwise/coordinator.py b/homeassistant/components/plugwise/coordinator.py index 1dff11d26d8..bc12ef4443b 100644 --- a/homeassistant/components/plugwise/coordinator.py +++ b/homeassistant/components/plugwise/coordinator.py @@ -7,6 +7,7 @@ from plugwise.exceptions import ( ConnectionFailedError, InvalidAuthentication, InvalidXMLError, + PlugwiseError, ResponseError, UnsupportedDeviceError, ) @@ -64,22 +65,23 @@ class PlugwiseDataUpdateCoordinator(DataUpdateCoordinator[PlugwiseData]): async def _async_update_data(self) -> PlugwiseData: """Fetch data from Plugwise.""" - + data = PlugwiseData({}, {}) try: if not self._connected: await self._connect() data = await self.api.async_update() + except ConnectionFailedError as err: + raise UpdateFailed("Failed to connect") from err except InvalidAuthentication as err: - raise ConfigEntryError("Invalid username or Smile ID") from err + raise ConfigEntryError("Authentication failed") from err except (InvalidXMLError, ResponseError) as err: raise UpdateFailed( - "Invalid XML data, or error indication received for the Plugwise" - " Adam/Smile/Stretch" + "Invalid XML data, or error indication received from the Plugwise Adam/Smile/Stretch" ) from err + except PlugwiseError as err: + raise UpdateFailed("Data incomplete or missing") from err except UnsupportedDeviceError as err: raise ConfigEntryError("Device with unsupported firmware") from err - except ConnectionFailedError as err: - raise UpdateFailed("Failed to connect to the Plugwise Smile") from err else: self.new_devices = set(data.devices) - self._current_devices self._current_devices = set(data.devices) diff --git a/homeassistant/components/plugwise/strings.json b/homeassistant/components/plugwise/strings.json index ef2d6458441..f74fc036e2a 100644 --- a/homeassistant/components/plugwise/strings.json +++ b/homeassistant/components/plugwise/strings.json @@ -55,6 +55,11 @@ "name": "Plugwise notification" } }, + "button": { + "reboot": { + "name": "Reboot" + } + }, "climate": { "plugwise": { "state_attributes": { diff --git a/tests/components/plugwise/test_button.py b/tests/components/plugwise/test_button.py new file mode 100644 index 00000000000..23003b3ffe6 --- /dev/null +++ b/tests/components/plugwise/test_button.py @@ -0,0 +1,39 @@ +"""Tests for Plugwise button entities.""" + +from unittest.mock import MagicMock + +from homeassistant.components.button import ( + DOMAIN as BUTTON_DOMAIN, + SERVICE_PRESS, + ButtonDeviceClass, +) +from homeassistant.const import ATTR_DEVICE_CLASS, ATTR_ENTITY_ID, STATE_UNKNOWN +from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er + +from tests.common import MockConfigEntry + + +async def test_adam_reboot_button( + hass: HomeAssistant, mock_smile_adam: MagicMock, init_integration: MockConfigEntry +) -> None: + """Test creation of button entities.""" + state = hass.states.get("button.adam_reboot") + assert state + assert state.state == STATE_UNKNOWN + assert state.attributes.get(ATTR_DEVICE_CLASS) == ButtonDeviceClass.RESTART + + registry = er.async_get(hass) + entry = registry.async_get("button.adam_reboot") + assert entry + assert entry.unique_id == "fe799307f1624099878210aa0b9f1475-reboot" + + await hass.services.async_call( + BUTTON_DOMAIN, + SERVICE_PRESS, + {ATTR_ENTITY_ID: "button.adam_reboot"}, + blocking=True, + ) + + assert mock_smile_adam.reboot_gateway.call_count == 1 + mock_smile_adam.reboot_gateway.assert_called_with() diff --git a/tests/components/plugwise/test_init.py b/tests/components/plugwise/test_init.py index 9c709f1c4f6..d3f23a18285 100644 --- a/tests/components/plugwise/test_init.py +++ b/tests/components/plugwise/test_init.py @@ -7,6 +7,7 @@ from plugwise.exceptions import ( ConnectionFailedError, InvalidAuthentication, InvalidXMLError, + PlugwiseError, ResponseError, UnsupportedDeviceError, ) @@ -83,6 +84,7 @@ async def test_load_unload_config_entry( (ConnectionFailedError, ConfigEntryState.SETUP_RETRY), (InvalidAuthentication, ConfigEntryState.SETUP_ERROR), (InvalidXMLError, ConfigEntryState.SETUP_RETRY), + (PlugwiseError, ConfigEntryState.SETUP_RETRY), (ResponseError, ConfigEntryState.SETUP_RETRY), (UnsupportedDeviceError, ConfigEntryState.SETUP_ERROR), ], @@ -219,7 +221,7 @@ async def test_update_device( entity_registry, mock_config_entry.entry_id ) ) - == 28 + == 29 ) assert ( len( @@ -242,7 +244,7 @@ async def test_update_device( entity_registry, mock_config_entry.entry_id ) ) - == 33 + == 34 ) assert ( len(