diff --git a/.strict-typing b/.strict-typing index 059ef850ab7..95d03544661 100644 --- a/.strict-typing +++ b/.strict-typing @@ -24,6 +24,7 @@ homeassistant.components.bmw_connected_drive.* homeassistant.components.bond.* homeassistant.components.braviatv.* homeassistant.components.brother.* +homeassistant.components.button.* homeassistant.components.calendar.* homeassistant.components.camera.* homeassistant.components.canary.* diff --git a/CODEOWNERS b/CODEOWNERS index 3534b73676d..21e6526b03f 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -85,6 +85,7 @@ homeassistant/components/brunt/* @eavanvalkenburg homeassistant/components/bsblan/* @liudger homeassistant/components/bt_smarthub/* @jxwolstenholme homeassistant/components/buienradar/* @mjj4791 @ties @Robbie1221 +homeassistant/components/button/* @home-assistant/core homeassistant/components/cast/* @emontnemery homeassistant/components/cert_expiry/* @Cereal2nd @jjlawren homeassistant/components/circuit/* @braam diff --git a/homeassistant/components/button/__init__.py b/homeassistant/components/button/__init__.py new file mode 100644 index 00000000000..fe2d1cb6435 --- /dev/null +++ b/homeassistant/components/button/__init__.py @@ -0,0 +1,104 @@ +"""Component to pressing a button as platforms.""" +from __future__ import annotations + +from dataclasses import dataclass +from datetime import datetime, timedelta +import logging +from typing import final + +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers.config_validation import ( # noqa: F401 + PLATFORM_SCHEMA, + PLATFORM_SCHEMA_BASE, +) +from homeassistant.helpers.entity import EntityDescription +from homeassistant.helpers.entity_component import EntityComponent +from homeassistant.helpers.restore_state import RestoreEntity +from homeassistant.helpers.typing import ConfigType +from homeassistant.util import dt as dt_util + +from .const import DOMAIN, SERVICE_PRESS + +SCAN_INTERVAL = timedelta(seconds=30) + +ENTITY_ID_FORMAT = DOMAIN + ".{}" + +MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10) + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: + """Set up Button entities.""" + component = hass.data[DOMAIN] = EntityComponent( + _LOGGER, DOMAIN, hass, SCAN_INTERVAL + ) + await component.async_setup(config) + + component.async_register_entity_service( + SERVICE_PRESS, + {}, + "_async_press_action", + ) + + return True + + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Set up a config entry.""" + component: EntityComponent = hass.data[DOMAIN] + return await component.async_setup_entry(entry) + + +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Unload a config entry.""" + component: EntityComponent = hass.data[DOMAIN] + return await component.async_unload_entry(entry) + + +@dataclass +class ButtonEntityDescription(EntityDescription): + """A class that describes button entities.""" + + +class ButtonEntity(RestoreEntity): + """Representation of a Button entity.""" + + entity_description: ButtonEntityDescription + _attr_should_poll = False + _attr_device_class: None = None + _attr_state: None = None + __last_pressed: datetime | None = None + + @property + @final + def state(self) -> str | None: + """Return the entity state.""" + if self.__last_pressed is None: + return None + return self.__last_pressed.isoformat() + + @final + async def _async_press_action(self) -> None: + """Press the button (from e.g., service call). + + Should not be overridden, handle setting last press timestamp. + """ + self.__last_pressed = dt_util.utcnow() + self.async_write_ha_state() + await self.async_press() + + async def async_added_to_hass(self) -> None: + """Call when the button is added to hass.""" + state = await self.async_get_last_state() + if state is not None and state.state is not None: + self.__last_pressed = dt_util.parse_datetime(state.state) + + def press(self) -> None: + """Press the button.""" + raise NotImplementedError() + + async def async_press(self) -> None: + """Press the button.""" + await self.hass.async_add_executor_job(self.press) diff --git a/homeassistant/components/button/const.py b/homeassistant/components/button/const.py new file mode 100644 index 00000000000..273314b2e97 --- /dev/null +++ b/homeassistant/components/button/const.py @@ -0,0 +1,4 @@ +"""Provides the constants needed for the component.""" + +DOMAIN = "button" +SERVICE_PRESS = "press" diff --git a/homeassistant/components/button/device_action.py b/homeassistant/components/button/device_action.py new file mode 100644 index 00000000000..2dffd9c600f --- /dev/null +++ b/homeassistant/components/button/device_action.py @@ -0,0 +1,58 @@ +"""Provides device actions for Button.""" +from __future__ import annotations + +import voluptuous as vol + +from homeassistant.const import ( + ATTR_ENTITY_ID, + CONF_DEVICE_ID, + CONF_DOMAIN, + CONF_ENTITY_ID, + CONF_TYPE, +) +from homeassistant.core import Context, HomeAssistant +from homeassistant.helpers import entity_registry +import homeassistant.helpers.config_validation as cv + +from .const import DOMAIN, SERVICE_PRESS + +ACTION_TYPES = {"press"} + +ACTION_SCHEMA = cv.DEVICE_ACTION_BASE_SCHEMA.extend( + { + vol.Required(CONF_TYPE): vol.In(ACTION_TYPES), + vol.Required(CONF_ENTITY_ID): cv.entity_domain(DOMAIN), + } +) + + +async def async_get_actions( + hass: HomeAssistant, device_id: str +) -> list[dict[str, str]]: + """List device actions for button devices.""" + registry = entity_registry.async_get(hass) + return [ + { + CONF_DEVICE_ID: device_id, + CONF_DOMAIN: DOMAIN, + CONF_ENTITY_ID: entry.entity_id, + CONF_TYPE: "press", + } + for entry in entity_registry.async_entries_for_device(registry, device_id) + if entry.domain == DOMAIN + ] + + +async def async_call_action_from_config( + hass: HomeAssistant, config: dict, variables: dict, context: Context | None +) -> None: + """Execute a device action.""" + await hass.services.async_call( + DOMAIN, + SERVICE_PRESS, + { + ATTR_ENTITY_ID: config[CONF_ENTITY_ID], + }, + blocking=True, + context=context, + ) diff --git a/homeassistant/components/button/device_trigger.py b/homeassistant/components/button/device_trigger.py new file mode 100644 index 00000000000..2005bc82194 --- /dev/null +++ b/homeassistant/components/button/device_trigger.py @@ -0,0 +1,73 @@ +"""Provides device triggers for Button.""" +from __future__ import annotations + +from typing import Any + +import voluptuous as vol + +from homeassistant.components.automation import ( + AutomationActionType, + AutomationTriggerInfo, +) +from homeassistant.components.device_automation import DEVICE_TRIGGER_BASE_SCHEMA +from homeassistant.components.homeassistant.triggers.state import ( + TRIGGER_SCHEMA as STATE_TRIGGER_SCHEMA, + async_attach_trigger as async_attach_state_trigger, +) +from homeassistant.const import ( + CONF_DEVICE_ID, + CONF_DOMAIN, + CONF_ENTITY_ID, + CONF_PLATFORM, + CONF_TYPE, +) +from homeassistant.core import CALLBACK_TYPE, HomeAssistant +from homeassistant.helpers import config_validation as cv, entity_registry +from homeassistant.helpers.typing import ConfigType + +from . import DOMAIN + +TRIGGER_TYPES = {"pressed"} + +TRIGGER_SCHEMA = DEVICE_TRIGGER_BASE_SCHEMA.extend( + { + vol.Required(CONF_ENTITY_ID): cv.entity_id, + vol.Required(CONF_TYPE): vol.In(TRIGGER_TYPES), + } +) + + +async def async_get_triggers( + hass: HomeAssistant, device_id: str +) -> list[dict[str, Any]]: + """List device triggers for button devices.""" + registry = entity_registry.async_get(hass) + return [ + { + CONF_PLATFORM: "device", + CONF_DEVICE_ID: device_id, + CONF_DOMAIN: DOMAIN, + CONF_ENTITY_ID: entry.entity_id, + CONF_TYPE: "pressed", + } + for entry in entity_registry.async_entries_for_device(registry, device_id) + if entry.domain == DOMAIN + ] + + +async def async_attach_trigger( + hass: HomeAssistant, + config: ConfigType, + action: AutomationActionType, + automation_info: AutomationTriggerInfo, +) -> CALLBACK_TYPE: + """Attach a trigger.""" + state_config = { + CONF_PLATFORM: "state", + CONF_ENTITY_ID: config[CONF_ENTITY_ID], + } + + state_config = STATE_TRIGGER_SCHEMA(state_config) + return await async_attach_state_trigger( + hass, state_config, action, automation_info, platform_type="device" + ) diff --git a/homeassistant/components/button/manifest.json b/homeassistant/components/button/manifest.json new file mode 100644 index 00000000000..beeaca487a6 --- /dev/null +++ b/homeassistant/components/button/manifest.json @@ -0,0 +1,7 @@ +{ + "domain": "button", + "name": "Button", + "documentation": "https://www.home-assistant.io/integrations/button", + "codeowners": ["@home-assistant/core"], + "quality_scale": "internal" +} diff --git a/homeassistant/components/button/services.yaml b/homeassistant/components/button/services.yaml new file mode 100644 index 00000000000..245368f9d5b --- /dev/null +++ b/homeassistant/components/button/services.yaml @@ -0,0 +1,6 @@ +press: + name: Press + description: Press the button entity. + target: + entity: + domain: button diff --git a/homeassistant/components/button/strings.json b/homeassistant/components/button/strings.json new file mode 100644 index 00000000000..ca774c57d77 --- /dev/null +++ b/homeassistant/components/button/strings.json @@ -0,0 +1,11 @@ +{ + "title": "Button", + "device_automation": { + "trigger_type": { + "pressed": "{entity_name} has been pressed" + }, + "action_type": { + "press": "Press {entity_name} button" + } + } +} diff --git a/homeassistant/components/button/translations/en.json b/homeassistant/components/button/translations/en.json new file mode 100644 index 00000000000..8b19cf25774 --- /dev/null +++ b/homeassistant/components/button/translations/en.json @@ -0,0 +1,11 @@ +{ + "device_automation": { + "action_type": { + "press": "Press {entity_name} button" + }, + "trigger_type": { + "pressed": "{entity_name} has been pressed" + } + }, + "title": "Button" +} \ No newline at end of file diff --git a/homeassistant/components/demo/__init__.py b/homeassistant/components/demo/__init__.py index db53c1c528f..952b7cbd9b2 100644 --- a/homeassistant/components/demo/__init__.py +++ b/homeassistant/components/demo/__init__.py @@ -15,6 +15,7 @@ COMPONENTS_WITH_CONFIG_ENTRY_DEMO_PLATFORM = [ "air_quality", "alarm_control_panel", "binary_sensor", + "button", "camera", "climate", "cover", diff --git a/homeassistant/components/demo/button.py b/homeassistant/components/demo/button.py new file mode 100644 index 00000000000..9ef54f30db3 --- /dev/null +++ b/homeassistant/components/demo/button.py @@ -0,0 +1,65 @@ +"""Demo platform that offers a fake button entity.""" +from __future__ import annotations + +from homeassistant.components.button import ButtonEntity +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import DEVICE_DEFAULT_NAME +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType + +from . import DOMAIN + + +async def async_setup_platform( + hass: HomeAssistant, + config: ConfigType, + async_add_entities: AddEntitiesCallback, + discovery_info: DiscoveryInfoType = None, +) -> None: + """Set up the demo Button entity.""" + async_add_entities( + [ + DemoButton( + unique_id="push", + name="Push", + icon="mdi:gesture-tap-button", + ), + ] + ) + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up the Demo config entry.""" + await async_setup_platform(hass, {}, async_add_entities) + + +class DemoButton(ButtonEntity): + """Representation of a demo button entity.""" + + _attr_should_poll = False + + def __init__( + self, + unique_id: str, + name: str, + icon: str, + ) -> None: + """Initialize the Demo button entity.""" + self._attr_unique_id = unique_id + self._attr_name = name or DEVICE_DEFAULT_NAME + self._attr_icon = icon + self._attr_device_info = { + "identifiers": {(DOMAIN, unique_id)}, + "name": name, + } + + async def async_press(self) -> None: + """Send out a persistent notification.""" + self.hass.components.persistent_notification.async_create( + "Button pressed", title="Button" + ) diff --git a/mypy.ini b/mypy.ini index f52ee13b689..086db083892 100644 --- a/mypy.ini +++ b/mypy.ini @@ -275,6 +275,17 @@ no_implicit_optional = true warn_return_any = true warn_unreachable = true +[mypy-homeassistant.components.button.*] +check_untyped_defs = true +disallow_incomplete_defs = true +disallow_subclassing_any = true +disallow_untyped_calls = true +disallow_untyped_decorators = true +disallow_untyped_defs = true +no_implicit_optional = true +warn_return_any = true +warn_unreachable = true + [mypy-homeassistant.components.calendar.*] check_untyped_defs = true disallow_incomplete_defs = true diff --git a/script/hassfest/manifest.py b/script/hassfest/manifest.py index abade24dbf9..0bec9e51436 100644 --- a/script/hassfest/manifest.py +++ b/script/hassfest/manifest.py @@ -38,6 +38,7 @@ NO_IOT_CLASS = [ "automation", "binary_sensor", "blueprint", + "button", "calendar", "camera", "climate", diff --git a/tests/components/button/__init__.py b/tests/components/button/__init__.py new file mode 100644 index 00000000000..77489053189 --- /dev/null +++ b/tests/components/button/__init__.py @@ -0,0 +1 @@ +"""The tests for the Button integration.""" diff --git a/tests/components/button/test_device_action.py b/tests/components/button/test_device_action.py new file mode 100644 index 00000000000..984be163d42 --- /dev/null +++ b/tests/components/button/test_device_action.py @@ -0,0 +1,87 @@ +"""The tests for Button device actions.""" +import pytest + +from homeassistant.components import automation +from homeassistant.components.button import DOMAIN +from homeassistant.core import HomeAssistant +from homeassistant.helpers import device_registry, entity_registry +from homeassistant.setup import async_setup_component + +from tests.common import ( + MockConfigEntry, + assert_lists_same, + async_get_device_automations, + async_mock_service, + mock_device_registry, + mock_registry, +) + + +@pytest.fixture +def device_reg(hass: HomeAssistant) -> device_registry.DeviceRegistry: + """Return an empty, loaded, registry.""" + return mock_device_registry(hass) + + +@pytest.fixture +def entity_reg(hass: HomeAssistant) -> entity_registry.EntityRegistry: + """Return an empty, loaded, registry.""" + return mock_registry(hass) + + +async def test_get_actions( + hass: HomeAssistant, + device_reg: device_registry.DeviceRegistry, + entity_reg: entity_registry.EntityRegistry, +) -> None: + """Test we get the expected actions from a button.""" + config_entry = MockConfigEntry(domain="test", data={}) + config_entry.add_to_hass(hass) + device_entry = device_reg.async_get_or_create( + config_entry_id=config_entry.entry_id, + connections={(device_registry.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")}, + ) + entity_reg.async_get_or_create(DOMAIN, "test", "5678", device_id=device_entry.id) + expected_actions = [ + { + "domain": DOMAIN, + "type": "press", + "device_id": device_entry.id, + "entity_id": "button.test_5678", + } + ] + actions = await async_get_device_automations(hass, "action", device_entry.id) + assert_lists_same(actions, expected_actions) + + +async def test_action(hass: HomeAssistant) -> None: + """Test for press action.""" + assert await async_setup_component( + hass, + automation.DOMAIN, + { + automation.DOMAIN: [ + { + "trigger": { + "platform": "event", + "event_type": "test_event", + }, + "action": { + "domain": DOMAIN, + "device_id": "abcdefgh", + "entity_id": "button.entity", + "type": "press", + }, + }, + ] + }, + ) + + press_calls = async_mock_service(hass, DOMAIN, "press") + + hass.bus.async_fire("test_event") + await hass.async_block_till_done() + assert len(press_calls) == 1 + assert press_calls[0].domain == DOMAIN + assert press_calls[0].service == "press" + assert press_calls[0].data == {"entity_id": "button.entity"} diff --git a/tests/components/button/test_device_trigger.py b/tests/components/button/test_device_trigger.py new file mode 100644 index 00000000000..913aec9088e --- /dev/null +++ b/tests/components/button/test_device_trigger.py @@ -0,0 +1,108 @@ +"""The tests for Button device triggers.""" +from __future__ import annotations + +import pytest + +from homeassistant.components import automation +from homeassistant.components.button import DOMAIN +from homeassistant.core import HomeAssistant, ServiceCall +from homeassistant.helpers import device_registry +from homeassistant.helpers.entity_registry import EntityRegistry +from homeassistant.setup import async_setup_component + +from tests.common import ( + MockConfigEntry, + assert_lists_same, + async_get_device_automations, + async_mock_service, + mock_device_registry, + mock_registry, +) + + +@pytest.fixture +def device_reg(hass: HomeAssistant) -> device_registry.DeviceRegistry: + """Return an empty, loaded, registry.""" + return mock_device_registry(hass) + + +@pytest.fixture +def entity_reg(hass: HomeAssistant) -> EntityRegistry: + """Return an empty, loaded, registry.""" + return mock_registry(hass) + + +@pytest.fixture +def calls(hass: HomeAssistant) -> list[ServiceCall]: + """Track calls to a mock service.""" + return async_mock_service(hass, "test", "automation") + + +async def test_get_triggers( + hass: HomeAssistant, + device_reg: device_registry.DeviceRegistry, + entity_reg: EntityRegistry, +) -> None: + """Test we get the expected triggers from a button.""" + config_entry = MockConfigEntry(domain="test", data={}) + config_entry.add_to_hass(hass) + device_entry = device_reg.async_get_or_create( + config_entry_id=config_entry.entry_id, + connections={(device_registry.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")}, + ) + entity_reg.async_get_or_create(DOMAIN, "test", "5678", device_id=device_entry.id) + expected_triggers = [ + { + "platform": "device", + "domain": DOMAIN, + "type": "pressed", + "device_id": device_entry.id, + "entity_id": f"{DOMAIN}.test_5678", + } + ] + triggers = await async_get_device_automations(hass, "trigger", device_entry.id) + assert_lists_same(triggers, expected_triggers) + + +async def test_if_fires_on_state_change(hass, calls): + """Test for turn_on and turn_off triggers firing.""" + hass.states.async_set("button.entity", "unknown") + + assert await async_setup_component( + hass, + automation.DOMAIN, + { + automation.DOMAIN: [ + { + "trigger": { + "platform": "device", + "domain": DOMAIN, + "device_id": "", + "entity_id": "button.entity", + "type": "pressed", + }, + "action": { + "service": "test.automation", + "data": { + "some": ( + "to - {{ trigger.platform}} - " + "{{ trigger.entity_id}} - {{ trigger.from_state.state}} - " + "{{ trigger.to_state.state}} - {{ trigger.for }} - " + "{{ trigger.id}}" + ) + }, + }, + } + ] + }, + ) + + # Test triggering device trigger with a to state + hass.states.async_set("button.entity", "2021-01-01T23:59:59+00:00") + await hass.async_block_till_done() + assert len(calls) == 1 + assert calls[0].data[ + "some" + ] == "to - device - {} - unknown - 2021-01-01T23:59:59+00:00 - None - 0".format( + "button.entity" + ) diff --git a/tests/components/button/test_init.py b/tests/components/button/test_init.py new file mode 100644 index 00000000000..2846a6f3a8d --- /dev/null +++ b/tests/components/button/test_init.py @@ -0,0 +1,64 @@ +"""The tests for the Button component.""" +from unittest.mock import MagicMock, patch + +import pytest + +from homeassistant.components.button import DOMAIN, SERVICE_PRESS, ButtonEntity +from homeassistant.const import ATTR_ENTITY_ID, CONF_PLATFORM, STATE_UNKNOWN +from homeassistant.core import HomeAssistant, State +from homeassistant.setup import async_setup_component +from homeassistant.util import dt as dt_util + +from tests.common import mock_restore_cache + + +async def test_button(hass: HomeAssistant) -> None: + """Test getting data from the mocked button entity.""" + button = ButtonEntity() + assert button.state is None + + button.hass = hass + + with pytest.raises(NotImplementedError): + await button.async_press() + + button.press = MagicMock() + await button.async_press() + + assert button.press.called + + +async def test_custom_integration(hass, caplog, enable_custom_integrations): + """Test we integration.""" + platform = getattr(hass.components, f"test.{DOMAIN}") + platform.init() + + assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) + await hass.async_block_till_done() + + assert hass.states.get("button.button_1").state == STATE_UNKNOWN + + now = dt_util.utcnow() + with patch("homeassistant.core.dt_util.utcnow", return_value=now): + await hass.services.async_call( + DOMAIN, + SERVICE_PRESS, + {ATTR_ENTITY_ID: "button.button_1"}, + blocking=True, + ) + + assert hass.states.get("button.button_1").state == now.isoformat() + assert "The button has been pressed" in caplog.text + + +async def test_restore_state(hass, enable_custom_integrations): + """Test we restore state integration.""" + mock_restore_cache(hass, (State("button.button_1", "2021-01-01T23:59:59+00:00"),)) + + platform = getattr(hass.components, f"test.{DOMAIN}") + platform.init() + + assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) + await hass.async_block_till_done() + + assert hass.states.get("button.button_1").state == "2021-01-01T23:59:59+00:00" diff --git a/tests/components/demo/test_button.py b/tests/components/demo/test_button.py new file mode 100644 index 00000000000..d4f6f97cb7a --- /dev/null +++ b/tests/components/demo/test_button.py @@ -0,0 +1,47 @@ +"""The tests for the demo button component.""" + +from unittest.mock import patch + +import pytest + +from homeassistant.components.button.const import DOMAIN, SERVICE_PRESS +from homeassistant.const import ATTR_ENTITY_ID, STATE_UNKNOWN +from homeassistant.core import HomeAssistant +from homeassistant.setup import async_setup_component +from homeassistant.util import dt as dt_util + +ENTITY_PUSH = "button.push" + + +@pytest.fixture(autouse=True) +async def setup_demo_button(hass: HomeAssistant) -> None: + """Initialize setup demo button entity.""" + assert await async_setup_component(hass, DOMAIN, {"button": {"platform": "demo"}}) + await hass.async_block_till_done() + + +def test_setup_params(hass: HomeAssistant) -> None: + """Test the initial parameters.""" + state = hass.states.get(ENTITY_PUSH) + assert state + assert state.state == STATE_UNKNOWN + + +async def test_press(hass: HomeAssistant) -> None: + """Test pressing the button.""" + state = hass.states.get(ENTITY_PUSH) + assert state + assert state.state == STATE_UNKNOWN + + now = dt_util.parse_datetime("2021-01-09 12:00:00+00:00") + with patch("homeassistant.util.dt.utcnow", return_value=now): + await hass.services.async_call( + DOMAIN, + SERVICE_PRESS, + {ATTR_ENTITY_ID: ENTITY_PUSH}, + blocking=True, + ) + + state = hass.states.get(ENTITY_PUSH) + assert state + assert state.state == now.isoformat() diff --git a/tests/testing_config/custom_components/test/button.py b/tests/testing_config/custom_components/test/button.py new file mode 100644 index 00000000000..471e0f42a39 --- /dev/null +++ b/tests/testing_config/custom_components/test/button.py @@ -0,0 +1,47 @@ +""" +Provide a mock button platform. + +Call init before using it in your tests to ensure clean test data. +""" +import logging + +from homeassistant.components.button import ButtonEntity + +from tests.common import MockEntity + +UNIQUE_BUTTON_1 = "unique_button_1" + +ENTITIES = [] + +_LOGGER = logging.getLogger(__name__) + + +class MockButtonEntity(MockEntity, ButtonEntity): + """Mock Button class.""" + + def press(self) -> None: + """Press the button.""" + _LOGGER.info("The button has been pressed") + + +def init(empty=False): + """Initialize the platform with entities.""" + global ENTITIES + + ENTITIES = ( + [] + if empty + else [ + MockButtonEntity( + name="button 1", + unique_id="unique_button_1", + ), + ] + ) + + +async def async_setup_platform( + hass, config, async_add_entities_callback, discovery_info=None +): + """Return mock entities.""" + async_add_entities_callback(ENTITIES)