From 80a74470306850817ce9292b7f6f10eaa818c405 Mon Sep 17 00:00:00 2001 From: Joakim Plate Date: Wed, 19 Jul 2023 10:17:40 +0200 Subject: [PATCH] Add support for buttons in gardena bluetooth (#96871) * Add button to gardena * Add tests for button * Bump gardena bluetooth to 1.0.2 --------- Co-authored-by: Joost Lekkerkerker --- .../components/gardena_bluetooth/__init__.py | 1 + .../components/gardena_bluetooth/button.py | 60 +++++++++++++++++ .../gardena_bluetooth/manifest.json | 2 +- .../components/gardena_bluetooth/strings.json | 5 ++ requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- .../snapshots/test_button.ambr | 25 +++++++ .../gardena_bluetooth/test_button.py | 67 +++++++++++++++++++ 8 files changed, 161 insertions(+), 3 deletions(-) create mode 100644 homeassistant/components/gardena_bluetooth/button.py create mode 100644 tests/components/gardena_bluetooth/snapshots/test_button.ambr create mode 100644 tests/components/gardena_bluetooth/test_button.py diff --git a/homeassistant/components/gardena_bluetooth/__init__.py b/homeassistant/components/gardena_bluetooth/__init__.py index c779d30b0fc..2390f5af561 100644 --- a/homeassistant/components/gardena_bluetooth/__init__.py +++ b/homeassistant/components/gardena_bluetooth/__init__.py @@ -22,6 +22,7 @@ from .coordinator import Coordinator, DeviceUnavailable PLATFORMS: list[Platform] = [ Platform.BINARY_SENSOR, + Platform.BUTTON, Platform.NUMBER, Platform.SENSOR, Platform.SWITCH, diff --git a/homeassistant/components/gardena_bluetooth/button.py b/homeassistant/components/gardena_bluetooth/button.py new file mode 100644 index 00000000000..cfaa4d72c2a --- /dev/null +++ b/homeassistant/components/gardena_bluetooth/button.py @@ -0,0 +1,60 @@ +"""Support for button entities.""" +from __future__ import annotations + +from dataclasses import dataclass, field + +from gardena_bluetooth.const import Reset +from gardena_bluetooth.parse import CharacteristicBool + +from homeassistant.components.button import ( + ButtonEntity, + ButtonEntityDescription, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import EntityCategory +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from .const import DOMAIN +from .coordinator import Coordinator, GardenaBluetoothDescriptorEntity + + +@dataclass +class GardenaBluetoothButtonEntityDescription(ButtonEntityDescription): + """Description of entity.""" + + char: CharacteristicBool = field(default_factory=lambda: CharacteristicBool("")) + + +DESCRIPTIONS = ( + GardenaBluetoothButtonEntityDescription( + key=Reset.factory_reset.uuid, + translation_key="factory_reset", + entity_category=EntityCategory.DIAGNOSTIC, + entity_registry_enabled_default=False, + char=Reset.factory_reset, + ), +) + + +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback +) -> None: + """Set up binary sensor based on a config entry.""" + coordinator: Coordinator = hass.data[DOMAIN][entry.entry_id] + entities = [ + GardenaBluetoothButton(coordinator, description) + for description in DESCRIPTIONS + if description.key in coordinator.characteristics + ] + async_add_entities(entities) + + +class GardenaBluetoothButton(GardenaBluetoothDescriptorEntity, ButtonEntity): + """Representation of a binary sensor.""" + + entity_description: GardenaBluetoothButtonEntityDescription + + async def async_press(self) -> None: + """Trigger button action.""" + await self.coordinator.write(self.entity_description.char, True) diff --git a/homeassistant/components/gardena_bluetooth/manifest.json b/homeassistant/components/gardena_bluetooth/manifest.json index cdc43a802c9..0226460d4d8 100644 --- a/homeassistant/components/gardena_bluetooth/manifest.json +++ b/homeassistant/components/gardena_bluetooth/manifest.json @@ -13,5 +13,5 @@ "dependencies": ["bluetooth_adapters"], "documentation": "https://www.home-assistant.io/integrations/gardena_bluetooth", "iot_class": "local_polling", - "requirements": ["gardena_bluetooth==1.0.1"] + "requirements": ["gardena_bluetooth==1.0.2"] } diff --git a/homeassistant/components/gardena_bluetooth/strings.json b/homeassistant/components/gardena_bluetooth/strings.json index 5a3f77eafa4..1d9a281fdbc 100644 --- a/homeassistant/components/gardena_bluetooth/strings.json +++ b/homeassistant/components/gardena_bluetooth/strings.json @@ -24,6 +24,11 @@ "name": "Valve connection" } }, + "button": { + "factory_reset": { + "name": "Factory reset" + } + }, "number": { "remaining_open_time": { "name": "Remaining open time" diff --git a/requirements_all.txt b/requirements_all.txt index 9065b918ba5..74a6082c0bc 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -820,7 +820,7 @@ fritzconnection[qr]==1.12.2 gTTS==2.2.4 # homeassistant.components.gardena_bluetooth -gardena_bluetooth==1.0.1 +gardena_bluetooth==1.0.2 # homeassistant.components.google_assistant_sdk gassist-text==0.0.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index e9b238f1d17..3b968ee932e 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -642,7 +642,7 @@ fritzconnection[qr]==1.12.2 gTTS==2.2.4 # homeassistant.components.gardena_bluetooth -gardena_bluetooth==1.0.1 +gardena_bluetooth==1.0.2 # homeassistant.components.google_assistant_sdk gassist-text==0.0.10 diff --git a/tests/components/gardena_bluetooth/snapshots/test_button.ambr b/tests/components/gardena_bluetooth/snapshots/test_button.ambr new file mode 100644 index 00000000000..b9cdca0e03c --- /dev/null +++ b/tests/components/gardena_bluetooth/snapshots/test_button.ambr @@ -0,0 +1,25 @@ +# serializer version: 1 +# name: test_setup + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Mock Title Factory reset', + }), + 'context': , + 'entity_id': 'button.mock_title_factory_reset', + 'last_changed': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_setup.1 + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Mock Title Factory reset', + }), + 'context': , + 'entity_id': 'button.mock_title_factory_reset', + 'last_changed': , + 'last_updated': , + 'state': 'unknown', + }) +# --- diff --git a/tests/components/gardena_bluetooth/test_button.py b/tests/components/gardena_bluetooth/test_button.py new file mode 100644 index 00000000000..e184a2ecce8 --- /dev/null +++ b/tests/components/gardena_bluetooth/test_button.py @@ -0,0 +1,67 @@ +"""Test Gardena Bluetooth sensor.""" + + +from unittest.mock import Mock, call + +from gardena_bluetooth.const import Reset +import pytest +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 . import setup_entry + +from tests.common import MockConfigEntry + + +@pytest.fixture +def mock_switch_chars(mock_read_char_raw): + """Mock data on device.""" + mock_read_char_raw[Reset.factory_reset.uuid] = b"\x00" + return mock_read_char_raw + + +async def test_setup( + hass: HomeAssistant, + snapshot: SnapshotAssertion, + mock_entry: MockConfigEntry, + mock_switch_chars: dict[str, bytes], +) -> None: + """Test setup creates expected entities.""" + + entity_id = "button.mock_title_factory_reset" + coordinator = await setup_entry(hass, mock_entry, [Platform.BUTTON]) + assert hass.states.get(entity_id) == snapshot + + mock_switch_chars[Reset.factory_reset.uuid] = b"\x01" + await coordinator.async_refresh() + assert hass.states.get(entity_id) == snapshot + + +async def test_switching( + hass: HomeAssistant, + mock_entry: MockConfigEntry, + mock_client: Mock, + mock_switch_chars: dict[str, bytes], +) -> None: + """Test switching makes correct calls.""" + + entity_id = "button.mock_title_factory_reset" + await setup_entry(hass, mock_entry, [Platform.BUTTON]) + assert hass.states.get(entity_id) + + await hass.services.async_call( + BUTTON_DOMAIN, + SERVICE_PRESS, + {ATTR_ENTITY_ID: entity_id}, + blocking=True, + ) + + assert mock_client.write_char.mock_calls == [ + call(Reset.factory_reset, True), + ]