Implement a reboot-button for Plugwise (#120554)

Co-authored-by: Franck Nijhof <frenck@frenck.nl>
This commit is contained in:
Bouwe Westerdijk 2024-06-26 15:28:50 +02:00 committed by GitHub
parent af9b4b98ca
commit 4defc4a58f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 111 additions and 8 deletions

View File

@ -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()

View File

@ -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,

View File

@ -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)

View File

@ -55,6 +55,11 @@
"name": "Plugwise notification"
}
},
"button": {
"reboot": {
"name": "Reboot"
}
},
"climate": {
"plugwise": {
"state_attributes": {

View File

@ -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()

View File

@ -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(