Home Assistant Unofficial Reference 2024.12.1
services.py
Go to the documentation of this file.
1 """Service calls related dependencies for LCN component."""
2 
3 from enum import StrEnum, auto
4 
5 import pypck
6 import voluptuous as vol
7 
8 from homeassistant.const import (
9  CONF_ADDRESS,
10  CONF_BRIGHTNESS,
11  CONF_HOST,
12  CONF_STATE,
13  CONF_UNIT_OF_MEASUREMENT,
14 )
15 from homeassistant.core import HomeAssistant, ServiceCall
17 
18 from .const import (
19  CONF_KEYS,
20  CONF_LED,
21  CONF_OUTPUT,
22  CONF_PCK,
23  CONF_RELVARREF,
24  CONF_ROW,
25  CONF_SETPOINT,
26  CONF_TABLE,
27  CONF_TEXT,
28  CONF_TIME,
29  CONF_TIME_UNIT,
30  CONF_TRANSITION,
31  CONF_VALUE,
32  CONF_VARIABLE,
33  DOMAIN,
34  LED_PORTS,
35  LED_STATUS,
36  OUTPUT_PORTS,
37  RELVARREF,
38  SENDKEYCOMMANDS,
39  SETPOINTS,
40  THRESHOLDS,
41  TIME_UNITS,
42  VAR_UNITS,
43  VARIABLES,
44 )
45 from .helpers import (
46  DeviceConnectionType,
47  get_device_connection,
48  is_address,
49  is_states_string,
50 )
51 
52 
54  """Parent class for all LCN service calls."""
55 
56  schema = vol.Schema({vol.Required(CONF_ADDRESS): is_address})
57 
58  def __init__(self, hass: HomeAssistant) -> None:
59  """Initialize service call."""
60  self.hasshass = hass
61 
62  def get_device_connection(self, service: ServiceCall) -> DeviceConnectionType:
63  """Get address connection object."""
64  address, host_name = service.data[CONF_ADDRESS]
65 
66  for config_entry in self.hasshass.config_entries.async_entries(DOMAIN):
67  if config_entry.data[CONF_HOST] == host_name:
68  device_connection = get_device_connection(
69  self.hasshass, address, config_entry
70  )
71  if device_connection is None:
72  raise ValueError("Wrong address.")
73  return device_connection
74  raise ValueError("Invalid host name.")
75 
76  async def async_call_service(self, service: ServiceCall) -> None:
77  """Execute service call."""
78  raise NotImplementedError
79 
80 
82  """Set absolute brightness of output port in percent."""
83 
84  schema = LcnServiceCall.schema.extend(
85  {
86  vol.Required(CONF_OUTPUT): vol.All(vol.Upper, vol.In(OUTPUT_PORTS)),
87  vol.Required(CONF_BRIGHTNESS): vol.All(
88  vol.Coerce(int), vol.Range(min=0, max=100)
89  ),
90  vol.Optional(CONF_TRANSITION, default=0): vol.All(
91  vol.Coerce(float), vol.Range(min=0.0, max=486.0)
92  ),
93  }
94  )
95 
96  async def async_call_service(self, service: ServiceCall) -> None:
97  """Execute service call."""
98  output = pypck.lcn_defs.OutputPort[service.data[CONF_OUTPUT]]
99  brightness = service.data[CONF_BRIGHTNESS]
100  transition = pypck.lcn_defs.time_to_ramp_value(
101  service.data[CONF_TRANSITION] * 1000
102  )
103 
104  device_connection = self.get_device_connectionget_device_connection(service)
105  await device_connection.dim_output(output.value, brightness, transition)
106 
107 
109  """Set relative brightness of output port in percent."""
110 
111  schema = LcnServiceCall.schema.extend(
112  {
113  vol.Required(CONF_OUTPUT): vol.All(vol.Upper, vol.In(OUTPUT_PORTS)),
114  vol.Required(CONF_BRIGHTNESS): vol.All(
115  vol.Coerce(int), vol.Range(min=-100, max=100)
116  ),
117  }
118  )
119 
120  async def async_call_service(self, service: ServiceCall) -> None:
121  """Execute service call."""
122  output = pypck.lcn_defs.OutputPort[service.data[CONF_OUTPUT]]
123  brightness = service.data[CONF_BRIGHTNESS]
124 
125  device_connection = self.get_device_connectionget_device_connection(service)
126  await device_connection.rel_output(output.value, brightness)
127 
128 
130  """Toggle output port."""
131 
132  schema = LcnServiceCall.schema.extend(
133  {
134  vol.Required(CONF_OUTPUT): vol.All(vol.Upper, vol.In(OUTPUT_PORTS)),
135  vol.Optional(CONF_TRANSITION, default=0): vol.All(
136  vol.Coerce(float), vol.Range(min=0.0, max=486.0)
137  ),
138  }
139  )
140 
141  async def async_call_service(self, service: ServiceCall) -> None:
142  """Execute service call."""
143  output = pypck.lcn_defs.OutputPort[service.data[CONF_OUTPUT]]
144  transition = pypck.lcn_defs.time_to_ramp_value(
145  service.data[CONF_TRANSITION] * 1000
146  )
147 
148  device_connection = self.get_device_connectionget_device_connection(service)
149  await device_connection.toggle_output(output.value, transition)
150 
151 
153  """Set the relays status."""
154 
155  schema = LcnServiceCall.schema.extend({vol.Required(CONF_STATE): is_states_string})
156 
157  async def async_call_service(self, service: ServiceCall) -> None:
158  """Execute service call."""
159  states = [
160  pypck.lcn_defs.RelayStateModifier[state]
161  for state in service.data[CONF_STATE]
162  ]
163 
164  device_connection = self.get_device_connectionget_device_connection(service)
165  await device_connection.control_relays(states)
166 
167 
169  """Set the led state."""
170 
171  schema = LcnServiceCall.schema.extend(
172  {
173  vol.Required(CONF_LED): vol.All(vol.Upper, vol.In(LED_PORTS)),
174  vol.Required(CONF_STATE): vol.All(vol.Upper, vol.In(LED_STATUS)),
175  }
176  )
177 
178  async def async_call_service(self, service: ServiceCall) -> None:
179  """Execute service call."""
180  led = pypck.lcn_defs.LedPort[service.data[CONF_LED]]
181  led_state = pypck.lcn_defs.LedStatus[service.data[CONF_STATE]]
182 
183  device_connection = self.get_device_connectionget_device_connection(service)
184  await device_connection.control_led(led, led_state)
185 
186 
188  """Set absolute value of a variable or setpoint.
189 
190  Variable has to be set as counter!
191  Regulator setpoints can also be set using R1VARSETPOINT, R2VARSETPOINT.
192  """
193 
194  schema = LcnServiceCall.schema.extend(
195  {
196  vol.Required(CONF_VARIABLE): vol.All(
197  vol.Upper, vol.In(VARIABLES + SETPOINTS)
198  ),
199  vol.Optional(CONF_VALUE, default=0): vol.Coerce(float),
200  vol.Optional(CONF_UNIT_OF_MEASUREMENT, default="native"): vol.All(
201  vol.Upper, vol.In(VAR_UNITS)
202  ),
203  }
204  )
205 
206  async def async_call_service(self, service: ServiceCall) -> None:
207  """Execute service call."""
208  var = pypck.lcn_defs.Var[service.data[CONF_VARIABLE]]
209  value = service.data[CONF_VALUE]
210  unit = pypck.lcn_defs.VarUnit.parse(service.data[CONF_UNIT_OF_MEASUREMENT])
211 
212  device_connection = self.get_device_connectionget_device_connection(service)
213  await device_connection.var_abs(var, value, unit)
214 
215 
217  """Reset value of variable or setpoint."""
218 
219  schema = LcnServiceCall.schema.extend(
220  {vol.Required(CONF_VARIABLE): vol.All(vol.Upper, vol.In(VARIABLES + SETPOINTS))}
221  )
222 
223  async def async_call_service(self, service: ServiceCall) -> None:
224  """Execute service call."""
225  var = pypck.lcn_defs.Var[service.data[CONF_VARIABLE]]
226 
227  device_connection = self.get_device_connectionget_device_connection(service)
228  await device_connection.var_reset(var)
229 
230 
232  """Shift value of a variable, setpoint or threshold."""
233 
234  schema = LcnServiceCall.schema.extend(
235  {
236  vol.Required(CONF_VARIABLE): vol.All(
237  vol.Upper, vol.In(VARIABLES + SETPOINTS + THRESHOLDS)
238  ),
239  vol.Optional(CONF_VALUE, default=0): vol.Coerce(float),
240  vol.Optional(CONF_UNIT_OF_MEASUREMENT, default="native"): vol.All(
241  vol.Upper, vol.In(VAR_UNITS)
242  ),
243  vol.Optional(CONF_RELVARREF, default="current"): vol.All(
244  vol.Upper, vol.In(RELVARREF)
245  ),
246  }
247  )
248 
249  async def async_call_service(self, service: ServiceCall) -> None:
250  """Execute service call."""
251  var = pypck.lcn_defs.Var[service.data[CONF_VARIABLE]]
252  value = service.data[CONF_VALUE]
253  unit = pypck.lcn_defs.VarUnit.parse(service.data[CONF_UNIT_OF_MEASUREMENT])
254  value_ref = pypck.lcn_defs.RelVarRef[service.data[CONF_RELVARREF]]
255 
256  device_connection = self.get_device_connectionget_device_connection(service)
257  await device_connection.var_rel(var, value, unit, value_ref)
258 
259 
261  """Locks a regulator setpoint."""
262 
263  schema = LcnServiceCall.schema.extend(
264  {
265  vol.Required(CONF_SETPOINT): vol.All(vol.Upper, vol.In(SETPOINTS)),
266  vol.Optional(CONF_STATE, default=False): bool,
267  }
268  )
269 
270  async def async_call_service(self, service: ServiceCall) -> None:
271  """Execute service call."""
272  setpoint = pypck.lcn_defs.Var[service.data[CONF_SETPOINT]]
273  state = service.data[CONF_STATE]
274 
275  reg_id = pypck.lcn_defs.Var.to_set_point_id(setpoint)
276  device_connection = self.get_device_connectionget_device_connection(service)
277  await device_connection.lock_regulator(reg_id, state)
278 
279 
281  """Sends keys (which executes bound commands)."""
282 
283  schema = LcnServiceCall.schema.extend(
284  {
285  vol.Required(CONF_KEYS): vol.All(
286  vol.Upper, cv.matches_regex(r"^([A-D][1-8])+$")
287  ),
288  vol.Optional(CONF_STATE, default="hit"): vol.All(
289  vol.Upper, vol.In(SENDKEYCOMMANDS)
290  ),
291  vol.Optional(CONF_TIME, default=0): cv.positive_int,
292  vol.Optional(CONF_TIME_UNIT, default="S"): vol.All(
293  vol.Upper, vol.In(TIME_UNITS)
294  ),
295  }
296  )
297 
298  async def async_call_service(self, service: ServiceCall) -> None:
299  """Execute service call."""
300  device_connection = self.get_device_connectionget_device_connection(service)
301 
302  keys = [[False] * 8 for i in range(4)]
303 
304  key_strings = zip(
305  service.data[CONF_KEYS][::2], service.data[CONF_KEYS][1::2], strict=False
306  )
307 
308  for table, key in key_strings:
309  table_id = ord(table) - 65
310  key_id = int(key) - 1
311  keys[table_id][key_id] = True
312 
313  if (delay_time := service.data[CONF_TIME]) != 0:
314  hit = pypck.lcn_defs.SendKeyCommand.HIT
315  if pypck.lcn_defs.SendKeyCommand[service.data[CONF_STATE]] != hit:
316  raise ValueError(
317  "Only hit command is allowed when sending deferred keys."
318  )
319  delay_unit = pypck.lcn_defs.TimeUnit.parse(service.data[CONF_TIME_UNIT])
320  await device_connection.send_keys_hit_deferred(keys, delay_time, delay_unit)
321  else:
322  state = pypck.lcn_defs.SendKeyCommand[service.data[CONF_STATE]]
323  await device_connection.send_keys(keys, state)
324 
325 
327  """Lock keys."""
328 
329  schema = LcnServiceCall.schema.extend(
330  {
331  vol.Optional(CONF_TABLE, default="a"): vol.All(
332  vol.Upper, cv.matches_regex(r"^[A-D]$")
333  ),
334  vol.Required(CONF_STATE): is_states_string,
335  vol.Optional(CONF_TIME, default=0): cv.positive_int,
336  vol.Optional(CONF_TIME_UNIT, default="S"): vol.All(
337  vol.Upper, vol.In(TIME_UNITS)
338  ),
339  }
340  )
341 
342  async def async_call_service(self, service: ServiceCall) -> None:
343  """Execute service call."""
344  device_connection = self.get_device_connectionget_device_connection(service)
345 
346  states = [
347  pypck.lcn_defs.KeyLockStateModifier[state]
348  for state in service.data[CONF_STATE]
349  ]
350  table_id = ord(service.data[CONF_TABLE]) - 65
351 
352  if (delay_time := service.data[CONF_TIME]) != 0:
353  if table_id != 0:
354  raise ValueError(
355  "Only table A is allowed when locking keys for a specific time."
356  )
357  delay_unit = pypck.lcn_defs.TimeUnit.parse(service.data[CONF_TIME_UNIT])
358  await device_connection.lock_keys_tab_a_temporary(
359  delay_time, delay_unit, states
360  )
361  else:
362  await device_connection.lock_keys(table_id, states)
363 
364  handler = device_connection.status_requests_handler
365  await handler.request_status_locked_keys_timeout()
366 
367 
369  """Send dynamic text to LCN-GTxD displays."""
370 
371  schema = LcnServiceCall.schema.extend(
372  {
373  vol.Required(CONF_ROW): vol.All(int, vol.Range(min=1, max=4)),
374  vol.Required(CONF_TEXT): vol.All(str, vol.Length(max=60)),
375  }
376  )
377 
378  async def async_call_service(self, service: ServiceCall) -> None:
379  """Execute service call."""
380  row_id = service.data[CONF_ROW] - 1
381  text = service.data[CONF_TEXT]
382 
383  device_connection = self.get_device_connectionget_device_connection(service)
384  await device_connection.dyn_text(row_id, text)
385 
386 
388  """Send arbitrary PCK command."""
389 
390  schema = LcnServiceCall.schema.extend({vol.Required(CONF_PCK): str})
391 
392  async def async_call_service(self, service: ServiceCall) -> None:
393  """Execute service call."""
394  pck = service.data[CONF_PCK]
395  device_connection = self.get_device_connectionget_device_connection(service)
396  await device_connection.pck(pck)
397 
398 
399 class LcnService(StrEnum):
400  """LCN service names."""
401 
402  OUTPUT_ABS = auto()
403  OUTPUT_REL = auto()
404  OUTPUT_TOGGLE = auto()
405  RELAYS = auto()
406  VAR_ABS = auto()
407  VAR_RESET = auto()
408  VAR_REL = auto()
409  LOCK_REGULATOR = auto()
410  LED = auto()
411  SEND_KEYS = auto()
412  LOCK_KEYS = auto()
413  DYN_TEXT = auto()
414  PCK = auto()
415 
416 
417 SERVICES = (
418  (LcnService.OUTPUT_ABS, OutputAbs),
419  (LcnService.OUTPUT_REL, OutputRel),
420  (LcnService.OUTPUT_TOGGLE, OutputToggle),
421  (LcnService.RELAYS, Relays),
422  (LcnService.VAR_ABS, VarAbs),
423  (LcnService.VAR_RESET, VarReset),
424  (LcnService.VAR_REL, VarRel),
425  (LcnService.LOCK_REGULATOR, LockRegulator),
426  (LcnService.LED, Led),
427  (LcnService.SEND_KEYS, SendKeys),
428  (LcnService.LOCK_KEYS, LockKeys),
429  (LcnService.DYN_TEXT, DynText),
430  (LcnService.PCK, Pck),
431 )
432 
433 
434 async def register_services(hass: HomeAssistant) -> None:
435  """Register services for LCN."""
436  for service_name, service in SERVICES:
437  hass.services.async_register(
438  DOMAIN, service_name, service(hass).async_call_service, service.schema
439  )
None async_call_service(self, ServiceCall service)
Definition: services.py:378
None async_call_service(self, ServiceCall service)
Definition: services.py:76
DeviceConnectionType get_device_connection(self, ServiceCall service)
Definition: services.py:62
None __init__(self, HomeAssistant hass)
Definition: services.py:58
None async_call_service(self, ServiceCall service)
Definition: services.py:178
None async_call_service(self, ServiceCall service)
Definition: services.py:342
None async_call_service(self, ServiceCall service)
Definition: services.py:270
None async_call_service(self, ServiceCall service)
Definition: services.py:96
None async_call_service(self, ServiceCall service)
Definition: services.py:120
None async_call_service(self, ServiceCall service)
Definition: services.py:141
None async_call_service(self, ServiceCall service)
Definition: services.py:392
None async_call_service(self, ServiceCall service)
Definition: services.py:157
None async_call_service(self, ServiceCall service)
Definition: services.py:298
None async_call_service(self, ServiceCall service)
Definition: services.py:206
None async_call_service(self, ServiceCall service)
Definition: services.py:249
None async_call_service(self, ServiceCall service)
Definition: services.py:223
None register_services(HomeAssistant hass)
Definition: services.py:434