From 4e0c981240dc8e1574da8cf50f5aa40262c19ea9 Mon Sep 17 00:00:00 2001 From: Jeroen de Jong Date: Tue, 4 Aug 2026 10:08:37 +0200 Subject: [PATCH] Add silence-alarm button to ToGrill (#177360) Co-authored-by: Claude Opus 4.8 (1M context) --- homeassistant/components/togrill/__init__.py | 1 + homeassistant/components/togrill/button.py | 57 +++++++++++++ homeassistant/components/togrill/icons.json | 5 ++ .../components/togrill/manifest.json | 2 +- homeassistant/components/togrill/strings.json | 5 ++ requirements_all.txt | 2 +- .../togrill/snapshots/test_button.ambr | 51 ++++++++++++ tests/components/togrill/test_button.py | 81 +++++++++++++++++++ 8 files changed, 202 insertions(+), 2 deletions(-) create mode 100644 homeassistant/components/togrill/button.py create mode 100644 tests/components/togrill/snapshots/test_button.ambr create mode 100644 tests/components/togrill/test_button.py diff --git a/homeassistant/components/togrill/__init__.py b/homeassistant/components/togrill/__init__.py index bea0797b6cb5..c7706e030a93 100644 --- a/homeassistant/components/togrill/__init__.py +++ b/homeassistant/components/togrill/__init__.py @@ -7,6 +7,7 @@ from homeassistant.exceptions import ConfigEntryNotReady from .coordinator import DeviceNotFound, ToGrillConfigEntry, ToGrillCoordinator _PLATFORMS: list[Platform] = [ + Platform.BUTTON, Platform.EVENT, Platform.NUMBER, Platform.SELECT, diff --git a/homeassistant/components/togrill/button.py b/homeassistant/components/togrill/button.py new file mode 100644 index 000000000000..89a78a74640f --- /dev/null +++ b/homeassistant/components/togrill/button.py @@ -0,0 +1,57 @@ +"""Support for button entities.""" + +from typing import override + +from togrill_bluetooth.packets import PacketA5Write + +from homeassistant.components.button import ButtonEntity, ButtonEntityDescription +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback + +from . import ToGrillConfigEntry +from .coordinator import ToGrillCoordinator +from .entity import ToGrillEntity + +PARALLEL_UPDATES = 0 + +ENTITY_DESCRIPTIONS = ( + ButtonEntityDescription( + key="silence", + translation_key="silence", + ), +) + + +async def async_setup_entry( + hass: HomeAssistant, + entry: ToGrillConfigEntry, + async_add_entities: AddConfigEntryEntitiesCallback, +) -> None: + """Set up button based on a config entry.""" + + coordinator = entry.runtime_data + + async_add_entities( + ToGrillButton(coordinator, entity_description) + for entity_description in ENTITY_DESCRIPTIONS + ) + + +class ToGrillButton(ToGrillEntity, ButtonEntity): + """Representation of a button.""" + + def __init__( + self, + coordinator: ToGrillCoordinator, + entity_description: ButtonEntityDescription, + ) -> None: + """Initialize.""" + + super().__init__(coordinator) + self.entity_description = entity_description + self._attr_unique_id = f"{coordinator.address}_{entity_description.key}" + + @override + async def async_press(self) -> None: + """Silence any active alarm on the device.""" + await self._write_packet(PacketA5Write()) diff --git a/homeassistant/components/togrill/icons.json b/homeassistant/components/togrill/icons.json index 55b6c504b7ea..f4486909e01d 100644 --- a/homeassistant/components/togrill/icons.json +++ b/homeassistant/components/togrill/icons.json @@ -1,5 +1,10 @@ { "entity": { + "button": { + "silence": { + "default": "mdi:alarm-off" + } + }, "select": { "grill_type": { "default": "mdi:grill", diff --git a/homeassistant/components/togrill/manifest.json b/homeassistant/components/togrill/manifest.json index 9897c9921d39..91500c1094a1 100644 --- a/homeassistant/components/togrill/manifest.json +++ b/homeassistant/components/togrill/manifest.json @@ -16,5 +16,5 @@ "iot_class": "local_push", "loggers": ["togrill_bluetooth"], "quality_scale": "bronze", - "requirements": ["togrill-bluetooth==0.8.1"] + "requirements": ["togrill-bluetooth==0.9.0"] } diff --git a/homeassistant/components/togrill/strings.json b/homeassistant/components/togrill/strings.json index 4f3b404c4e0d..b79095377105 100644 --- a/homeassistant/components/togrill/strings.json +++ b/homeassistant/components/togrill/strings.json @@ -28,6 +28,11 @@ } }, "entity": { + "button": { + "silence": { + "name": "Silence alarm" + } + }, "event": { "event": { "name": "Event", diff --git a/requirements_all.txt b/requirements_all.txt index 08acfb05ff54..86989e72426c 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -3208,7 +3208,7 @@ tmb==0.0.4 todoist-api-python==3.1.0 # homeassistant.components.togrill -togrill-bluetooth==0.8.1 +togrill-bluetooth==0.9.0 # homeassistant.components.tolo tololib==1.2.2 diff --git a/tests/components/togrill/snapshots/test_button.ambr b/tests/components/togrill/snapshots/test_button.ambr new file mode 100644 index 000000000000..0cea6ea669f3 --- /dev/null +++ b/tests/components/togrill/snapshots/test_button.ambr @@ -0,0 +1,51 @@ +# serializer version: 1 +# name: test_setup[button.pro_05_silence_alarm-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'button', + 'entity_category': None, + 'entity_id': 'button.pro_05_silence_alarm', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Silence alarm', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Silence alarm', + 'platform': 'togrill', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'silence', + 'unique_id': '00000000-0000-0000-0000-000000000001_silence', + 'unit_of_measurement': None, + }) +# --- +# name: test_setup[button.pro_05_silence_alarm-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'Pro-05 Silence alarm', + }), + 'context': , + 'entity_id': 'button.pro_05_silence_alarm', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- diff --git a/tests/components/togrill/test_button.py b/tests/components/togrill/test_button.py new file mode 100644 index 000000000000..78e5af3a6f9e --- /dev/null +++ b/tests/components/togrill/test_button.py @@ -0,0 +1,81 @@ +"""Test buttons for ToGrill integration.""" + +from unittest.mock import Mock + +import pytest +from syrupy.assertion import SnapshotAssertion +from togrill_bluetooth.packets import PacketA5Write + +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 TOGRILL_SERVICE_INFO, setup_entry + +from tests.common import MockConfigEntry, snapshot_platform +from tests.components.bluetooth import inject_bluetooth_service_info + + +async def test_setup( + hass: HomeAssistant, + entity_registry: er.EntityRegistry, + snapshot: SnapshotAssertion, + mock_entry: MockConfigEntry, + mock_client: Mock, +) -> None: + """Test the buttons.""" + + inject_bluetooth_service_info(hass, TOGRILL_SERVICE_INFO) + + await setup_entry(hass, mock_entry, [Platform.BUTTON]) + + await snapshot_platform(hass, entity_registry, snapshot, mock_entry.entry_id) + + +async def test_press( + hass: HomeAssistant, + mock_entry: MockConfigEntry, + mock_client: Mock, +) -> None: + """Test pressing the silence button.""" + + inject_bluetooth_service_info(hass, TOGRILL_SERVICE_INFO) + + await setup_entry(hass, mock_entry, [Platform.BUTTON]) + + await hass.services.async_call( + BUTTON_DOMAIN, + SERVICE_PRESS, + target={ + ATTR_ENTITY_ID: "button.pro_05_silence_alarm", + }, + blocking=True, + ) + + mock_client.write.assert_any_call(PacketA5Write()) + + +async def test_press_disconnected( + hass: HomeAssistant, + mock_entry: MockConfigEntry, + mock_client: Mock, +) -> None: + """Test pressing the button while disconnected raises.""" + + inject_bluetooth_service_info(hass, TOGRILL_SERVICE_INFO) + + await setup_entry(hass, mock_entry, [Platform.BUTTON]) + + mock_client.is_connected = False + + with pytest.raises(HomeAssistantError): + await hass.services.async_call( + BUTTON_DOMAIN, + SERVICE_PRESS, + target={ + ATTR_ENTITY_ID: "button.pro_05_silence_alarm", + }, + blocking=True, + )