Home Assistant Unofficial Reference 2024.12.1
sensor.py
Go to the documentation of this file.
1 """PrusaLink sensors."""
2 
3 from __future__ import annotations
4 
5 from collections.abc import Callable
6 from dataclasses import dataclass
7 from datetime import datetime, timedelta
8 from typing import Generic, TypeVar, cast
9 
10 from pyprusalink.types import JobInfo, PrinterInfo, PrinterState, PrinterStatus
11 from pyprusalink.types_legacy import LegacyPrinterStatus
12 
14  SensorDeviceClass,
15  SensorEntity,
16  SensorEntityDescription,
17  SensorStateClass,
18 )
19 from homeassistant.config_entries import ConfigEntry
20 from homeassistant.const import (
21  PERCENTAGE,
22  REVOLUTIONS_PER_MINUTE,
23  UnitOfLength,
24  UnitOfTemperature,
25 )
26 from homeassistant.core import HomeAssistant
27 from homeassistant.helpers.entity_platform import AddEntitiesCallback
28 from homeassistant.helpers.typing import StateType
29 from homeassistant.util.dt import utcnow
30 from homeassistant.util.variance import ignore_variance
31 
32 from .const import DOMAIN
33 from .coordinator import PrusaLinkUpdateCoordinator
34 from .entity import PrusaLinkEntity
35 
36 T = TypeVar("T", PrinterStatus, LegacyPrinterStatus, JobInfo, PrinterInfo)
37 
38 
39 @dataclass(frozen=True)
41  """Mixin for required keys."""
42 
43  value_fn: Callable[[T], datetime | StateType]
44 
45 
46 @dataclass(frozen=True)
48  SensorEntityDescription, PrusaLinkSensorEntityDescriptionMixin[T], Generic[T]
49 ):
50  """Describes PrusaLink sensor entity."""
51 
52  available_fn: Callable[[T], bool] = lambda _: True
53 
54 
55 SENSORS: dict[str, tuple[PrusaLinkSensorEntityDescription, ...]] = {
56  "status": (
57  PrusaLinkSensorEntityDescription[PrinterStatus](
58  key="printer.state",
59  name=None,
60  value_fn=lambda data: (cast(str, data["printer"]["state"].lower())),
61  device_class=SensorDeviceClass.ENUM,
62  options=[state.value.lower() for state in PrinterState],
63  translation_key="printer_state",
64  ),
65  PrusaLinkSensorEntityDescription[PrinterStatus](
66  key="printer.telemetry.temp-bed",
67  translation_key="heatbed_temperature",
68  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
69  device_class=SensorDeviceClass.TEMPERATURE,
70  state_class=SensorStateClass.MEASUREMENT,
71  value_fn=lambda data: cast(float, data["printer"]["temp_bed"]),
72  entity_registry_enabled_default=False,
73  ),
74  PrusaLinkSensorEntityDescription[PrinterStatus](
75  key="printer.telemetry.temp-nozzle",
76  translation_key="nozzle_temperature",
77  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
78  device_class=SensorDeviceClass.TEMPERATURE,
79  state_class=SensorStateClass.MEASUREMENT,
80  value_fn=lambda data: cast(float, data["printer"]["temp_nozzle"]),
81  entity_registry_enabled_default=False,
82  ),
83  PrusaLinkSensorEntityDescription[PrinterStatus](
84  key="printer.telemetry.temp-bed.target",
85  translation_key="heatbed_target_temperature",
86  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
87  device_class=SensorDeviceClass.TEMPERATURE,
88  state_class=SensorStateClass.MEASUREMENT,
89  value_fn=lambda data: cast(float, data["printer"]["target_bed"]),
90  entity_registry_enabled_default=False,
91  ),
92  PrusaLinkSensorEntityDescription[PrinterStatus](
93  key="printer.telemetry.temp-nozzle.target",
94  translation_key="nozzle_target_temperature",
95  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
96  device_class=SensorDeviceClass.TEMPERATURE,
97  state_class=SensorStateClass.MEASUREMENT,
98  value_fn=lambda data: cast(float, data["printer"]["target_nozzle"]),
99  entity_registry_enabled_default=False,
100  ),
101  PrusaLinkSensorEntityDescription[PrinterStatus](
102  key="printer.telemetry.z-height",
103  translation_key="z_height",
104  native_unit_of_measurement=UnitOfLength.MILLIMETERS,
105  device_class=SensorDeviceClass.DISTANCE,
106  state_class=SensorStateClass.MEASUREMENT,
107  value_fn=lambda data: cast(float, data["printer"]["axis_z"]),
108  entity_registry_enabled_default=False,
109  ),
110  PrusaLinkSensorEntityDescription[PrinterStatus](
111  key="printer.telemetry.print-speed",
112  translation_key="print_speed",
113  native_unit_of_measurement=PERCENTAGE,
114  value_fn=lambda data: cast(float, data["printer"]["speed"]),
115  ),
116  PrusaLinkSensorEntityDescription[PrinterStatus](
117  key="printer.telemetry.print-flow",
118  translation_key="print_flow",
119  native_unit_of_measurement=PERCENTAGE,
120  value_fn=lambda data: cast(float, data["printer"]["flow"]),
121  entity_registry_enabled_default=False,
122  ),
123  PrusaLinkSensorEntityDescription[PrinterStatus](
124  key="printer.telemetry.fan-hotend",
125  translation_key="fan_hotend",
126  native_unit_of_measurement=REVOLUTIONS_PER_MINUTE,
127  value_fn=lambda data: cast(float, data["printer"]["fan_hotend"]),
128  entity_registry_enabled_default=False,
129  ),
130  PrusaLinkSensorEntityDescription[PrinterStatus](
131  key="printer.telemetry.fan-print",
132  translation_key="fan_print",
133  native_unit_of_measurement=REVOLUTIONS_PER_MINUTE,
134  value_fn=lambda data: cast(float, data["printer"]["fan_print"]),
135  entity_registry_enabled_default=False,
136  ),
137  ),
138  "legacy_status": (
139  PrusaLinkSensorEntityDescription[LegacyPrinterStatus](
140  key="printer.telemetry.material",
141  translation_key="material",
142  value_fn=lambda data: cast(str, data["telemetry"]["material"]),
143  ),
144  ),
145  "job": (
146  PrusaLinkSensorEntityDescription[JobInfo](
147  key="job.progress",
148  translation_key="progress",
149  native_unit_of_measurement=PERCENTAGE,
150  value_fn=lambda data: cast(float, data["progress"]),
151  available_fn=lambda data: (
152  data.get("progress") is not None
153  and data.get("state") != PrinterState.IDLE.value
154  ),
155  ),
156  PrusaLinkSensorEntityDescription[JobInfo](
157  key="job.filename",
158  translation_key="filename",
159  value_fn=lambda data: cast(str, data["file"]["display_name"]),
160  available_fn=lambda data: (
161  data.get("file") is not None
162  and data.get("state") != PrinterState.IDLE.value
163  ),
164  ),
165  PrusaLinkSensorEntityDescription[JobInfo](
166  key="job.start",
167  translation_key="print_start",
168  device_class=SensorDeviceClass.TIMESTAMP,
169  value_fn=ignore_variance(
170  lambda data: (utcnow() - timedelta(seconds=data["time_printing"])),
171  timedelta(minutes=2),
172  ),
173  available_fn=lambda data: (
174  data.get("time_printing") is not None
175  and data.get("state") != PrinterState.IDLE.value
176  ),
177  ),
178  PrusaLinkSensorEntityDescription[JobInfo](
179  key="job.finish",
180  translation_key="print_finish",
181  device_class=SensorDeviceClass.TIMESTAMP,
182  value_fn=ignore_variance(
183  lambda data: (utcnow() + timedelta(seconds=data["time_remaining"])),
184  timedelta(minutes=2),
185  ),
186  available_fn=lambda data: (
187  data.get("time_remaining") is not None
188  and data.get("state") != PrinterState.IDLE.value
189  ),
190  ),
191  ),
192  "info": (
193  PrusaLinkSensorEntityDescription[PrinterInfo](
194  key="info.nozzle_diameter",
195  translation_key="nozzle_diameter",
196  native_unit_of_measurement=UnitOfLength.MILLIMETERS,
197  device_class=SensorDeviceClass.DISTANCE,
198  value_fn=lambda data: cast(str, data["nozzle_diameter"]),
199  entity_registry_enabled_default=False,
200  ),
201  ),
202 }
203 
204 
206  hass: HomeAssistant,
207  entry: ConfigEntry,
208  async_add_entities: AddEntitiesCallback,
209 ) -> None:
210  """Set up PrusaLink sensor based on a config entry."""
211  coordinators: dict[str, PrusaLinkUpdateCoordinator] = hass.data[DOMAIN][
212  entry.entry_id
213  ]
214 
215  entities: list[PrusaLinkEntity] = []
216 
217  for coordinator_type, sensors in SENSORS.items():
218  coordinator = coordinators[coordinator_type]
219  entities.extend(
220  PrusaLinkSensorEntity(coordinator, sensor_description)
221  for sensor_description in sensors
222  )
223 
224  async_add_entities(entities)
225 
226 
228  """Defines a PrusaLink sensor."""
229 
230  entity_description: PrusaLinkSensorEntityDescription
231 
232  def __init__(
233  self,
234  coordinator: PrusaLinkUpdateCoordinator,
235  description: PrusaLinkSensorEntityDescription,
236  ) -> None:
237  """Initialize a PrusaLink sensor entity."""
238  super().__init__(coordinator=coordinator)
239  self.entity_descriptionentity_description = description
240  self._attr_unique_id_attr_unique_id = f"{coordinator.config_entry.entry_id}_{description.key}"
241 
242  @property
243  def native_value(self) -> datetime | StateType:
244  """Return the state of the sensor."""
245  return self.entity_descriptionentity_description.value_fn(self.coordinator.data)
246 
247  @property
248  def available(self) -> bool:
249  """Return if sensor is available."""
250  return super().available and self.entity_descriptionentity_description.available_fn(
251  self.coordinator.data
252  )