Home Assistant Unofficial Reference 2024.12.1
sensor.py
Go to the documentation of this file.
1 """Support for monitoring OctoPrint sensors."""
2 
3 from __future__ import annotations
4 
5 from datetime import datetime, timedelta
6 import logging
7 
8 from pyoctoprintapi import OctoprintJobInfo, OctoprintPrinterInfo
9 
11  SensorDeviceClass,
12  SensorEntity,
13  SensorStateClass,
14 )
15 from homeassistant.config_entries import ConfigEntry
16 from homeassistant.const import PERCENTAGE, UnitOfTemperature
17 from homeassistant.core import HomeAssistant, callback
18 from homeassistant.helpers.entity_platform import AddEntitiesCallback
19 from homeassistant.helpers.update_coordinator import CoordinatorEntity
20 
21 from . import OctoprintDataUpdateCoordinator
22 from .const import DOMAIN
23 
24 _LOGGER = logging.getLogger(__name__)
25 
26 JOB_PRINTING_STATES = ["Printing from SD", "Printing"]
27 
28 
29 def _is_printer_printing(printer: OctoprintPrinterInfo) -> bool:
30  return (
31  printer
32  and printer.state
33  and printer.state.flags
34  and printer.state.flags.printing
35  )
36 
37 
39  hass: HomeAssistant,
40  config_entry: ConfigEntry,
41  async_add_entities: AddEntitiesCallback,
42 ) -> None:
43  """Set up the available OctoPrint binary sensors."""
44  coordinator: OctoprintDataUpdateCoordinator = hass.data[DOMAIN][
45  config_entry.entry_id
46  ]["coordinator"]
47  device_id = config_entry.unique_id
48 
49  assert device_id is not None
50 
51  known_tools = set()
52 
53  @callback
54  def async_add_tool_sensors() -> None:
55  if not coordinator.data["printer"]:
56  return
57 
58  new_tools: list[OctoPrintTemperatureSensor] = []
59  for tool in [
60  tool
61  for tool in coordinator.data["printer"].temperatures
62  if tool.name not in known_tools
63  ]:
64  assert device_id is not None
65  known_tools.add(tool.name)
66  new_tools.extend(
68  coordinator,
69  tool.name,
70  temp_type,
71  device_id,
72  )
73  for temp_type in ("actual", "target")
74  )
75  async_add_entities(new_tools)
76 
77  config_entry.async_on_unload(coordinator.async_add_listener(async_add_tool_sensors))
78 
79  if coordinator.data["printer"]:
80  async_add_tool_sensors()
81 
82  entities: list[SensorEntity] = [
83  OctoPrintStatusSensor(coordinator, device_id),
84  OctoPrintJobPercentageSensor(coordinator, device_id),
85  OctoPrintEstimatedFinishTimeSensor(coordinator, device_id),
86  OctoPrintStartTimeSensor(coordinator, device_id),
87  ]
88 
89  async_add_entities(entities)
90 
91 
93  CoordinatorEntity[OctoprintDataUpdateCoordinator], SensorEntity
94 ):
95  """Representation of an OctoPrint sensor."""
96 
97  def __init__(
98  self,
99  coordinator: OctoprintDataUpdateCoordinator,
100  sensor_type: str,
101  device_id: str,
102  ) -> None:
103  """Initialize a new OctoPrint sensor."""
104  super().__init__(coordinator)
105  self._device_id_device_id = device_id
106  self._attr_name_attr_name = f"OctoPrint {sensor_type}"
107  self._attr_unique_id_attr_unique_id = f"{sensor_type}-{device_id}"
108  self._attr_device_info_attr_device_info = coordinator.device_info
109 
110 
112  """Representation of an OctoPrint sensor."""
113 
114  _attr_icon = "mdi:printer-3d"
115 
116  def __init__(
117  self, coordinator: OctoprintDataUpdateCoordinator, device_id: str
118  ) -> None:
119  """Initialize a new OctoPrint sensor."""
120  super().__init__(coordinator, "Current State", device_id)
121 
122  @property
123  def native_value(self):
124  """Return sensor state."""
125  printer: OctoprintPrinterInfo = self.coordinator.data["printer"]
126  if not printer:
127  return None
128 
129  return printer.state.text
130 
131  @property
132  def available(self) -> bool:
133  """Return if entity is available."""
134  return self.coordinator.last_update_success and self.coordinator.data["printer"]
135 
136 
138  """Representation of an OctoPrint sensor."""
139 
140  _attr_native_unit_of_measurement = PERCENTAGE
141  _attr_icon = "mdi:file-percent"
142 
143  def __init__(
144  self, coordinator: OctoprintDataUpdateCoordinator, device_id: str
145  ) -> None:
146  """Initialize a new OctoPrint sensor."""
147  super().__init__(coordinator, "Job Percentage", device_id)
148 
149  @property
150  def native_value(self):
151  """Return sensor state."""
152  job: OctoprintJobInfo = self.coordinator.data["job"]
153  if not job:
154  return None
155 
156  if not (state := job.progress.completion):
157  return 0
158 
159  return round(state, 2)
160 
161 
163  """Representation of an OctoPrint sensor."""
164 
165  _attr_device_class = SensorDeviceClass.TIMESTAMP
166 
167  def __init__(
168  self, coordinator: OctoprintDataUpdateCoordinator, device_id: str
169  ) -> None:
170  """Initialize a new OctoPrint sensor."""
171  super().__init__(coordinator, "Estimated Finish Time", device_id)
172 
173  @property
174  def native_value(self) -> datetime | None:
175  """Return sensor state."""
176  job: OctoprintJobInfo = self.coordinator.data["job"]
177  if (
178  not job
179  or not job.progress.print_time_left
180  or not _is_printer_printing(self.coordinator.data["printer"])
181  ):
182  return None
183 
184  read_time = self.coordinator.data["last_read_time"]
185 
186  return (read_time + timedelta(seconds=job.progress.print_time_left)).replace(
187  second=0
188  )
189 
190 
192  """Representation of an OctoPrint sensor."""
193 
194  _attr_device_class = SensorDeviceClass.TIMESTAMP
195 
196  def __init__(
197  self, coordinator: OctoprintDataUpdateCoordinator, device_id: str
198  ) -> None:
199  """Initialize a new OctoPrint sensor."""
200  super().__init__(coordinator, "Start Time", device_id)
201 
202  @property
203  def native_value(self) -> datetime | None:
204  """Return sensor state."""
205  job: OctoprintJobInfo = self.coordinator.data["job"]
206 
207  if (
208  not job
209  or not job.progress.print_time
210  or not _is_printer_printing(self.coordinator.data["printer"])
211  ):
212  return None
213 
214  read_time = self.coordinator.data["last_read_time"]
215 
216  return (read_time - timedelta(seconds=job.progress.print_time)).replace(
217  second=0
218  )
219 
220 
222  """Representation of an OctoPrint sensor."""
223 
224  _attr_native_unit_of_measurement = UnitOfTemperature.CELSIUS
225  _attr_device_class = SensorDeviceClass.TEMPERATURE
226  _attr_state_class = SensorStateClass.MEASUREMENT
227 
228  def __init__(
229  self,
230  coordinator: OctoprintDataUpdateCoordinator,
231  tool: str,
232  temp_type: str,
233  device_id: str,
234  ) -> None:
235  """Initialize a new OctoPrint sensor."""
236  super().__init__(coordinator, f"{temp_type} {tool} temp", device_id)
237  self._temp_type_temp_type = temp_type
238  self._api_tool_api_tool = tool
239 
240  @property
241  def native_value(self):
242  """Return sensor state."""
243  printer: OctoprintPrinterInfo = self.coordinator.data["printer"]
244  if not printer:
245  return None
246 
247  for temp in printer.temperatures:
248  if temp.name == self._api_tool_api_tool:
249  val = (
250  temp.actual_temp
251  if self._temp_type_temp_type == "actual"
252  else temp.target_temp
253  )
254  if val is None:
255  return None
256 
257  return round(val, 2)
258 
259  return None
260 
261  @property
262  def available(self) -> bool:
263  """Return if entity is available."""
264  return self.coordinator.last_update_success and self.coordinator.data["printer"]
None __init__(self, OctoprintDataUpdateCoordinator coordinator, str device_id)
Definition: sensor.py:169
None __init__(self, OctoprintDataUpdateCoordinator coordinator, str device_id)
Definition: sensor.py:145
None __init__(self, OctoprintDataUpdateCoordinator coordinator, str sensor_type, str device_id)
Definition: sensor.py:102
None __init__(self, OctoprintDataUpdateCoordinator coordinator, str device_id)
Definition: sensor.py:198
None __init__(self, OctoprintDataUpdateCoordinator coordinator, str device_id)
Definition: sensor.py:118
None __init__(self, OctoprintDataUpdateCoordinator coordinator, str tool, str temp_type, str device_id)
Definition: sensor.py:234
bool _is_printer_printing(OctoprintPrinterInfo printer)
Definition: sensor.py:29
None async_setup_entry(HomeAssistant hass, ConfigEntry config_entry, AddEntitiesCallback async_add_entities)
Definition: sensor.py:42