From cd26de73b4a12c6d77077e4500d48767b3723e67 Mon Sep 17 00:00:00 2001 From: Robert Hillis Date: Tue, 27 Jun 2023 11:44:29 -0400 Subject: [PATCH] Add button platform to Dremel 3D printer (#94517) Co-authored-by: Franck Nijhof --- .../components/dremel_3d_printer/__init__.py | 2 +- .../components/dremel_3d_printer/button.py | 78 +++++++++++++++++++ .../components/dremel_3d_printer/strings.json | 11 +++ .../dremel_3d_printer/test_button.py | 56 +++++++++++++ 4 files changed, 146 insertions(+), 1 deletion(-) create mode 100644 homeassistant/components/dremel_3d_printer/button.py create mode 100644 tests/components/dremel_3d_printer/test_button.py diff --git a/homeassistant/components/dremel_3d_printer/__init__.py b/homeassistant/components/dremel_3d_printer/__init__.py index db17e594cc4..5f9f10dc9c1 100644 --- a/homeassistant/components/dremel_3d_printer/__init__.py +++ b/homeassistant/components/dremel_3d_printer/__init__.py @@ -12,7 +12,7 @@ from homeassistant.exceptions import ConfigEntryNotReady from .const import CAMERA_MODEL, DOMAIN from .coordinator import Dremel3DPrinterDataUpdateCoordinator -PLATFORMS = [Platform.BINARY_SENSOR, Platform.CAMERA, Platform.SENSOR] +PLATFORMS = [Platform.BINARY_SENSOR, Platform.BUTTON, Platform.CAMERA, Platform.SENSOR] async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: diff --git a/homeassistant/components/dremel_3d_printer/button.py b/homeassistant/components/dremel_3d_printer/button.py new file mode 100644 index 00000000000..2d328b30cea --- /dev/null +++ b/homeassistant/components/dremel_3d_printer/button.py @@ -0,0 +1,78 @@ +"""Support for Dremel 3D Printer buttons.""" +from __future__ import annotations + +from collections.abc import Callable +from dataclasses import dataclass + +from dremel3dpy import Dremel3DPrinter + +from homeassistant.components.button import ButtonEntity, ButtonEntityDescription +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from .const import DOMAIN +from .entity import Dremel3DPrinterEntity + + +@dataclass +class Dremel3DPrinterButtonEntityMixin: + """Mixin for required keys.""" + + press_fn: Callable[[Dremel3DPrinter], None] + + +@dataclass +class Dremel3DPrinterButtonEntityDescription( + ButtonEntityDescription, Dremel3DPrinterButtonEntityMixin +): + """Describes a Dremel 3D Printer button entity.""" + + +BUTTON_TYPES: tuple[Dremel3DPrinterButtonEntityDescription, ...] = ( + Dremel3DPrinterButtonEntityDescription( + key="cancel_job", + translation_key="cancel_job", + press_fn=lambda api: api.stop_print(), + ), + Dremel3DPrinterButtonEntityDescription( + key="pause_job", + translation_key="pause_job", + press_fn=lambda api: api.pause_print(), + ), + Dremel3DPrinterButtonEntityDescription( + key="resume_job", + translation_key="resume_job", + press_fn=lambda api: api.resume_print(), + ), +) + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up Dremel 3D Printer control buttons.""" + coordinator = hass.data[DOMAIN][config_entry.entry_id] + async_add_entities( + Dremel3DPrinterButtonEntity(coordinator, description) + for description in BUTTON_TYPES + ) + + +class Dremel3DPrinterButtonEntity(Dremel3DPrinterEntity, ButtonEntity): + """Represent a Dremel 3D Printer button.""" + + entity_description: Dremel3DPrinterButtonEntityDescription + + def press(self) -> None: + """Handle the button press.""" + # api does not care about the current state + try: + self.entity_description.press_fn(self._api) + except RuntimeError as ex: + raise HomeAssistantError( + "An error occurred while submitting command" + ) from ex diff --git a/homeassistant/components/dremel_3d_printer/strings.json b/homeassistant/components/dremel_3d_printer/strings.json index 77a9eac7a13..0016b8f2bca 100644 --- a/homeassistant/components/dremel_3d_printer/strings.json +++ b/homeassistant/components/dremel_3d_printer/strings.json @@ -16,6 +16,17 @@ } }, "entity": { + "button": { + "cancel_job": { + "name": "Cancel job" + }, + "pause_job": { + "name": "Pause job" + }, + "resume_job": { + "name": "Resume job" + } + }, "sensor": { "job_phase": { "name": "Job phase" diff --git a/tests/components/dremel_3d_printer/test_button.py b/tests/components/dremel_3d_printer/test_button.py new file mode 100644 index 00000000000..00102b3306b --- /dev/null +++ b/tests/components/dremel_3d_printer/test_button.py @@ -0,0 +1,56 @@ +"""Button tests for the Dremel 3D Printer integration.""" +from unittest.mock import AsyncMock, patch + +import pytest + +from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN, SERVICE_PRESS +from homeassistant.components.dremel_3d_printer.const import DOMAIN +from homeassistant.const import ATTR_ENTITY_ID +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError +from homeassistant.setup import async_setup_component + +from tests.common import MockConfigEntry + + +@pytest.mark.parametrize( + ("button", "function"), + ( + ("cancel", "stop"), + ("pause", "pause"), + ("resume", "resume"), + ), +) +async def test_buttons( + hass: HomeAssistant, + connection: None, + config_entry: MockConfigEntry, + entity_registry_enabled_by_default: AsyncMock, + button: str, + function: str, +) -> None: + """Test button entities function.""" + await hass.config_entries.async_setup(config_entry.entry_id) + assert await async_setup_component(hass, DOMAIN, {}) + with patch( + f"homeassistant.components.dremel_3d_printer.Dremel3DPrinter.{function}_print" + ) as mock: + await hass.services.async_call( + BUTTON_DOMAIN, + SERVICE_PRESS, + {ATTR_ENTITY_ID: [f"button.dremel_3d45_{button}_job"]}, + blocking=True, + ) + assert mock.call_count == 1 + + with patch( + f"homeassistant.components.dremel_3d_printer.Dremel3DPrinter.{function}_print", + side_effect=RuntimeError, + ) as mock, pytest.raises(HomeAssistantError): + await hass.services.async_call( + BUTTON_DOMAIN, + SERVICE_PRESS, + {ATTR_ENTITY_ID: [f"button.dremel_3d45_{button}_job"]}, + blocking=True, + ) + assert mock.call_count == 1