Home Assistant Unofficial Reference 2024.12.1
button.py
Go to the documentation of this file.
1 """PrusaLink sensors."""
2 
3 from __future__ import annotations
4 
5 from collections.abc import Callable, Coroutine
6 from dataclasses import dataclass
7 from typing import Any, Generic, TypeVar, cast
8 
9 from pyprusalink import JobInfo, LegacyPrinterStatus, PrinterStatus, PrusaLink
10 from pyprusalink.types import Conflict, PrinterState
11 
12 from homeassistant.components.button import ButtonEntity, ButtonEntityDescription
13 from homeassistant.config_entries import ConfigEntry
14 from homeassistant.core import HomeAssistant
15 from homeassistant.exceptions import HomeAssistantError
16 from homeassistant.helpers.entity_platform import AddEntitiesCallback
17 
18 from .const import DOMAIN
19 from .coordinator import PrusaLinkUpdateCoordinator
20 from .entity import PrusaLinkEntity
21 
22 T = TypeVar("T", PrinterStatus, LegacyPrinterStatus, JobInfo)
23 
24 
25 @dataclass(frozen=True)
27  """Mixin for required keys."""
28 
29  press_fn: Callable[[PrusaLink], Callable[[int], Coroutine[Any, Any, None]]]
30 
31 
32 @dataclass(frozen=True)
34  ButtonEntityDescription, PrusaLinkButtonEntityDescriptionMixin[T], Generic[T]
35 ):
36  """Describes PrusaLink button entity."""
37 
38  available_fn: Callable[[T], bool] = lambda _: True
39 
40 
41 BUTTONS: dict[str, tuple[PrusaLinkButtonEntityDescription, ...]] = {
42  "status": (
43  PrusaLinkButtonEntityDescription[PrinterStatus](
44  key="printer.cancel_job",
45  translation_key="cancel_job",
46  press_fn=lambda api: api.cancel_job,
47  available_fn=lambda data: (
48  data["printer"]["state"]
49  in [PrinterState.PRINTING.value, PrinterState.PAUSED.value]
50  ),
51  ),
52  PrusaLinkButtonEntityDescription[PrinterStatus](
53  key="job.pause_job",
54  translation_key="pause_job",
55  press_fn=lambda api: api.pause_job,
56  available_fn=lambda data: cast(
57  bool, data["printer"]["state"] == PrinterState.PRINTING.value
58  ),
59  ),
60  PrusaLinkButtonEntityDescription[PrinterStatus](
61  key="job.resume_job",
62  translation_key="resume_job",
63  press_fn=lambda api: api.resume_job,
64  available_fn=lambda data: cast(
65  bool, data["printer"]["state"] == PrinterState.PAUSED.value
66  ),
67  ),
68  ),
69 }
70 
71 
73  hass: HomeAssistant,
74  entry: ConfigEntry,
75  async_add_entities: AddEntitiesCallback,
76 ) -> None:
77  """Set up PrusaLink buttons based on a config entry."""
78  coordinators: dict[str, PrusaLinkUpdateCoordinator] = hass.data[DOMAIN][
79  entry.entry_id
80  ]
81 
82  entities: list[PrusaLinkEntity] = []
83 
84  for coordinator_type, sensors in BUTTONS.items():
85  coordinator = coordinators[coordinator_type]
86  entities.extend(
87  PrusaLinkButtonEntity(coordinator, sensor_description)
88  for sensor_description in sensors
89  )
90 
91  async_add_entities(entities)
92 
93 
95  """Defines a PrusaLink button."""
96 
97  entity_description: PrusaLinkButtonEntityDescription
98 
99  def __init__(
100  self,
101  coordinator: PrusaLinkUpdateCoordinator,
102  description: PrusaLinkButtonEntityDescription,
103  ) -> None:
104  """Initialize a PrusaLink sensor entity."""
105  super().__init__(coordinator=coordinator)
106  self.entity_descriptionentity_description = description
107  self._attr_unique_id_attr_unique_id = f"{coordinator.config_entry.entry_id}_{description.key}"
108 
109  @property
110  def available(self) -> bool:
111  """Return if sensor is available."""
112  return super().available and self.entity_descriptionentity_description.available_fn(
113  self.coordinator.data
114  )
115 
116  async def async_press(self) -> None:
117  """Press the button."""
118  job_id = self.coordinator.data["job"]["id"]
119  func = self.entity_descriptionentity_description.press_fn(self.coordinator.api)
120  try:
121  await func(job_id)
122  except Conflict as err:
123  raise HomeAssistantError(
124  "Action conflicts with current printer state"
125  ) from err
126 
127  coordinators: dict[str, PrusaLinkUpdateCoordinator] = self.hasshasshass.data[DOMAIN][
128  self.coordinator.config_entry.entry_id
129  ]
130 
131  for coordinator in coordinators.values():
132  coordinator.expect_change()
133  await coordinator.async_request_refresh()