snoo: add button entity for calling start_snoo (#151052)

Co-authored-by: Joostlek <joostlek@outlook.com>
This commit is contained in:
falconindy
2025-09-30 16:57:58 +02:00
committed by GitHub
co-authored by Joostlek
parent 914990b58a
commit 93ee6322f2
6 changed files with 177 additions and 0 deletions
@@ -19,6 +19,7 @@ _LOGGER = logging.getLogger(__name__)
PLATFORMS: list[Platform] = [
Platform.BINARY_SENSOR,
Platform.BUTTON,
Platform.EVENT,
Platform.SELECT,
Platform.SENSOR,
+69
View File
@@ -0,0 +1,69 @@
"""Support for Snoo Buttons."""
from collections.abc import Awaitable, Callable
from dataclasses import dataclass
from python_snoo.containers import SnooDevice
from python_snoo.exceptions import SnooCommandException
from python_snoo.snoo import Snoo
from homeassistant.components.button import ButtonEntity, ButtonEntityDescription
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .const import DOMAIN
from .coordinator import SnooConfigEntry
from .entity import SnooDescriptionEntity
@dataclass(kw_only=True, frozen=True)
class SnooButtonEntityDescription(ButtonEntityDescription):
"""Description for Snoo button entities."""
press_fn: Callable[[Snoo, SnooDevice], Awaitable[None]]
BUTTON_DESCRIPTIONS: list[SnooButtonEntityDescription] = [
SnooButtonEntityDescription(
key="start_snoo",
translation_key="start_snoo",
press_fn=lambda snoo, device: snoo.start_snoo(
device,
),
),
]
async def async_setup_entry(
hass: HomeAssistant,
entry: SnooConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up buttons for Snoo device."""
coordinators = entry.runtime_data
async_add_entities(
SnooButton(coordinator, description)
for coordinator in coordinators.values()
for description in BUTTON_DESCRIPTIONS
)
class SnooButton(SnooDescriptionEntity, ButtonEntity):
"""Representation of a Snoo button."""
entity_description: SnooButtonEntityDescription
async def async_press(self) -> None:
"""Handle the button press."""
try:
await self.entity_description.press_fn(
self.coordinator.snoo,
self.coordinator.device,
)
except SnooCommandException as err:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key=f"{self.entity_description.key}_failed",
translation_placeholders={"name": str(self.name)},
) from err
+9
View File
@@ -0,0 +1,9 @@
{
"entity": {
"button": {
"start_snoo": {
"default": "mdi:play"
}
}
}
}
@@ -25,6 +25,9 @@
"select_failed": {
"message": "Error while updating {name} to {option}"
},
"start_snoo_failed": {
"message": "Starting {name} failed"
},
"switch_on_failed": {
"message": "Turning {name} on failed"
},
@@ -41,6 +44,11 @@
"name": "Right safety clip"
}
},
"button": {
"start_snoo": {
"name": "Start"
}
},
"event": {
"event": {
"name": "Snoo event",
@@ -0,0 +1,49 @@
# serializer version: 1
# name: test_entities[button.test_snoo_start-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'button',
'entity_category': None,
'entity_id': 'button.test_snoo_start',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Start',
'platform': 'snoo',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'start_snoo',
'unique_id': 'random_num_start_snoo',
'unit_of_measurement': None,
})
# ---
# name: test_entities[button.test_snoo_start-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Test Snoo Start',
}),
'context': <ANY>,
'entity_id': 'button.test_snoo_start',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unavailable',
})
# ---
+41
View File
@@ -0,0 +1,41 @@
"""Test Snoo Buttons."""
from unittest.mock import AsyncMock, patch
from syrupy.assertion 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.helpers import entity_registry as er
from . import async_init_integration
from tests.common import snapshot_platform
async def test_entities(
hass: HomeAssistant,
bypass_api: AsyncMock,
snapshot: SnapshotAssertion,
entity_registry: er.EntityRegistry,
) -> None:
"""Test buttons."""
with patch("homeassistant.components.snoo.PLATFORMS", [Platform.BUTTON]):
entry = await async_init_integration(hass)
await snapshot_platform(hass, entity_registry, snapshot, entry.entry_id)
async def test_button_starts_snoo(hass: HomeAssistant, bypass_api: AsyncMock) -> None:
"""Test start_snoo button works correctly."""
await async_init_integration(hass)
await hass.services.async_call(
BUTTON_DOMAIN,
SERVICE_PRESS,
{ATTR_ENTITY_ID: "button.test_snoo_start"},
blocking=True,
)
assert bypass_api.start_snoo.assert_called_once