Home Assistant Unofficial Reference 2024.12.1
__init__.py
Go to the documentation of this file.
1 """Support for Repetier-Server sensors."""
2 
3 from __future__ import annotations
4 
5 from dataclasses import dataclass
6 from datetime import timedelta
7 import logging
8 
9 import pyrepetierng as pyrepetier
10 import voluptuous as vol
11 
12 from homeassistant.components.sensor import SensorDeviceClass, SensorEntityDescription
13 from homeassistant.const import (
14  CONF_API_KEY,
15  CONF_HOST,
16  CONF_MONITORED_CONDITIONS,
17  CONF_NAME,
18  CONF_PORT,
19  CONF_SENSORS,
20  PERCENTAGE,
21  UnitOfTemperature,
22 )
23 from homeassistant.core import HomeAssistant
25 from homeassistant.helpers.discovery import load_platform
26 from homeassistant.helpers.dispatcher import dispatcher_send
27 from homeassistant.helpers.event import track_time_interval
28 from homeassistant.helpers.typing import ConfigType
29 from homeassistant.util import slugify as util_slugify
30 
31 _LOGGER = logging.getLogger(__name__)
32 
33 DEFAULT_NAME = "RepetierServer"
34 DOMAIN = "repetier"
35 REPETIER_API = "repetier_api"
36 SCAN_INTERVAL = timedelta(seconds=10)
37 UPDATE_SIGNAL = "repetier_update_signal"
38 
39 TEMP_DATA = {"tempset": "temp_set", "tempread": "state", "output": "output"}
40 
41 
42 @dataclass
43 class APIMethods:
44  """API methods for properties."""
45 
46  offline: dict[str, str | None]
47  state: dict[str, str]
48  temp_data: dict[str, str] | None = None
49  attribute: str | None = None
50 
51 
52 API_PRINTER_METHODS: dict[str, APIMethods] = {
53  "bed_temperature": APIMethods(
54  offline={"heatedbeds": None, "state": "off"},
55  state={"heatedbeds": "temp_data"},
56  temp_data=TEMP_DATA,
57  attribute="heatedbeds",
58  ),
59  "extruder_temperature": APIMethods(
60  offline={"extruder": None, "state": "off"},
61  state={"extruder": "temp_data"},
62  temp_data=TEMP_DATA,
63  attribute="extruder",
64  ),
65  "chamber_temperature": APIMethods(
66  offline={"heatedchambers": None, "state": "off"},
67  state={"heatedchambers": "temp_data"},
68  temp_data=TEMP_DATA,
69  attribute="heatedchambers",
70  ),
71  "current_state": APIMethods(
72  offline={"state": None},
73  state={
74  "state": "state",
75  "activeextruder": "active_extruder",
76  "hasxhome": "x_homed",
77  "hasyhome": "y_homed",
78  "haszhome": "z_homed",
79  "firmware": "firmware",
80  "firmwareurl": "firmware_url",
81  },
82  ),
83  "current_job": APIMethods(
84  offline={"job": None, "state": "off"},
85  state={
86  "done": "state",
87  "job": "job_name",
88  "jobid": "job_id",
89  "totallines": "total_lines",
90  "linessent": "lines_sent",
91  "oflayer": "total_layers",
92  "layer": "current_layer",
93  "speedmultiply": "feed_rate",
94  "flowmultiply": "flow",
95  "x": "x",
96  "y": "y",
97  "z": "z",
98  },
99  ),
100  "job_end": APIMethods(
101  offline={"job": None, "state": "off", "start": None, "printtime": None},
102  state={
103  "job": "job_name",
104  "start": "start",
105  "printtime": "print_time",
106  "printedtimecomp": "from_start",
107  },
108  ),
109  "job_start": APIMethods(
110  offline={
111  "job": None,
112  "state": "off",
113  "start": None,
114  "printedtimecomp": None,
115  },
116  state={"job": "job_name", "start": "start", "printedtimecomp": "from_start"},
117  ),
118 }
119 
120 
122  """Validate that printers have an unique name."""
123  names = [util_slugify(printer[CONF_NAME]) for printer in value]
124  vol.Schema(vol.Unique())(names)
125  return value
126 
127 
128 @dataclass(frozen=True)
130  """Mixin for required keys."""
131 
132  type: str
133 
134 
135 @dataclass(frozen=True)
136 # pylint: disable-next=hass-enforce-class-module
138  SensorEntityDescription, RepetierRequiredKeysMixin
139 ):
140  """Describes Repetier sensor entity."""
141 
142 
143 SENSOR_TYPES: dict[str, RepetierSensorEntityDescription] = {
144  "bed_temperature": RepetierSensorEntityDescription(
145  key="bed_temperature",
146  type="temperature",
147  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
148  name="_bed_",
149  device_class=SensorDeviceClass.TEMPERATURE,
150  ),
151  "extruder_temperature": RepetierSensorEntityDescription(
152  key="extruder_temperature",
153  type="temperature",
154  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
155  name="_extruder_",
156  device_class=SensorDeviceClass.TEMPERATURE,
157  ),
158  "chamber_temperature": RepetierSensorEntityDescription(
159  key="chamber_temperature",
160  type="temperature",
161  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
162  name="_chamber_",
163  device_class=SensorDeviceClass.TEMPERATURE,
164  ),
165  "current_state": RepetierSensorEntityDescription(
166  key="current_state",
167  type="state",
168  icon="mdi:printer-3d",
169  ),
170  "current_job": RepetierSensorEntityDescription(
171  key="current_job",
172  type="progress",
173  native_unit_of_measurement=PERCENTAGE,
174  icon="mdi:file-percent",
175  name="_current_job",
176  ),
178  key="job_end",
179  type="progress",
180  icon="mdi:clock-end",
181  name="_job_end",
182  ),
183  "job_start": RepetierSensorEntityDescription(
184  key="job_start",
185  type="progress",
186  icon="mdi:clock-start",
187  name="_job_start",
188  ),
189 }
190 
191 SENSOR_SCHEMA = vol.Schema(
192  {
193  vol.Optional(CONF_MONITORED_CONDITIONS, default=list(SENSOR_TYPES)): vol.All(
194  cv.ensure_list, [vol.In(SENSOR_TYPES)]
195  ),
196  vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
197  }
198 )
199 
200 CONFIG_SCHEMA = vol.Schema(
201  {
202  DOMAIN: vol.All(
203  cv.ensure_list,
204  [
205  vol.Schema(
206  {
207  vol.Required(CONF_API_KEY): cv.string,
208  vol.Required(CONF_HOST): cv.string,
209  vol.Optional(CONF_PORT, default=3344): cv.port,
210  vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
211  vol.Optional(CONF_SENSORS, default={}): SENSOR_SCHEMA,
212  }
213  )
214  ],
215  has_all_unique_names,
216  )
217  },
218  extra=vol.ALLOW_EXTRA,
219 )
220 
221 
222 def setup(hass: HomeAssistant, config: ConfigType) -> bool:
223  """Set up the Repetier Server component."""
224  hass.data[REPETIER_API] = {}
225 
226  for repetier in config[DOMAIN]:
227  _LOGGER.debug("Repetier server config %s", repetier[CONF_HOST])
228 
229  url = f"http://{repetier[CONF_HOST]}"
230  port = repetier[CONF_PORT]
231  api_key = repetier[CONF_API_KEY]
232 
233  client = pyrepetier.Repetier(url=url, port=port, apikey=api_key)
234 
235  printers = client.getprinters()
236 
237  if not printers:
238  return False
239 
240  sensors = repetier[CONF_SENSORS][CONF_MONITORED_CONDITIONS]
241  api = PrinterAPI(hass, client, printers, sensors, repetier[CONF_NAME], config)
242  api.update()
243  track_time_interval(hass, api.update, SCAN_INTERVAL)
244 
245  hass.data[REPETIER_API][repetier[CONF_NAME]] = api
246 
247  return True
248 
249 
251  """Handle the printer API."""
252 
253  def __init__(self, hass, client, printers, sensors, conf_name, config):
254  """Set up instance."""
255  self._hass_hass = hass
256  self._client_client = client
257  self.printersprinters = printers
258  self.sensorssensors = sensors
259  self.conf_nameconf_name = conf_name
260  self.configconfig = config
261  self._known_entities_known_entities = set()
262 
263  def get_data(self, printer_id, sensor_type, temp_id):
264  """Get data from the state cache."""
265  printer = self.printersprinters[printer_id]
266  methods = API_PRINTER_METHODS[sensor_type]
267  for prop, offline in methods.offline.items():
268  if getattr(printer, prop) == offline:
269  # if state matches offline, sensor is offline
270  return None
271 
272  data = {}
273  for prop, attr in methods.state.items():
274  prop_data = getattr(printer, prop)
275  if attr == "temp_data":
276  temp_methods = methods.temp_data or {}
277  for temp_prop, temp_attr in temp_methods.items():
278  data[temp_attr] = getattr(prop_data[temp_id], temp_prop)
279  else:
280  data[attr] = prop_data
281  return data
282 
283  def update(self, now=None):
284  """Update the state cache from the printer API."""
285  for printer in self.printersprinters:
286  printer.get_data()
287  self._load_entities_load_entities()
288  dispatcher_send(self._hass_hass, UPDATE_SIGNAL)
289 
290  def _load_entities(self):
291  sensor_info = []
292  for pidx, printer in enumerate(self.printersprinters):
293  for sensor_type in self.sensorssensors:
294  info = {}
295  info["sensor_type"] = sensor_type
296  info["printer_id"] = pidx
297  info["name"] = printer.slug
298  info["printer_name"] = self.conf_nameconf_name
299 
300  known = f"{printer.slug}-{sensor_type}"
301  if known in self._known_entities_known_entities:
302  continue
303 
304  methods = API_PRINTER_METHODS[sensor_type]
305  if "temp_data" in methods.state.values():
306  prop_data = getattr(printer, methods.attribute or "")
307  if prop_data is None:
308  continue
309  for idx, _ in enumerate(prop_data):
310  prop_info = info.copy()
311  prop_info["temp_id"] = idx
312  sensor_info.append(prop_info)
313  else:
314  info["temp_id"] = None
315  sensor_info.append(info)
316  self._known_entities_known_entities.add(known)
317 
318  if not sensor_info:
319  return
321  self._hass_hass, "sensor", DOMAIN, {"sensors": sensor_info}, self.configconfig
322  )
def get_data(self, printer_id, sensor_type, temp_id)
Definition: __init__.py:263
def __init__(self, hass, client, printers, sensors, conf_name, config)
Definition: __init__.py:253
bool add(self, _T matcher)
Definition: match.py:185
bool setup(HomeAssistant hass, ConfigType config)
Definition: __init__.py:222
None load_platform(core.HomeAssistant hass, Platform|str component, str platform, DiscoveryInfoType|None discovered, ConfigType hass_config)
Definition: discovery.py:137
None dispatcher_send(HomeAssistant hass, str signal, *Any args)
Definition: dispatcher.py:137