Home Assistant Unofficial Reference 2024.12.1
config_flow.py
Go to the documentation of this file.
1 """Config flow for RFXCOM RFXtrx integration."""
2 
3 from __future__ import annotations
4 
5 import asyncio
6 from contextlib import suppress
7 import copy
8 import itertools
9 import os
10 from typing import Any, TypedDict, cast
11 
12 import RFXtrx as rfxtrxmod
13 import serial
14 import serial.tools.list_ports
15 import voluptuous as vol
16 
17 from homeassistant.config_entries import (
18  ConfigEntry,
19  ConfigFlow,
20  ConfigFlowResult,
21  OptionsFlow,
22 )
23 from homeassistant.const import (
24  CONF_COMMAND_OFF,
25  CONF_COMMAND_ON,
26  CONF_DEVICE,
27  CONF_DEVICE_ID,
28  CONF_DEVICES,
29  CONF_HOST,
30  CONF_PORT,
31  CONF_TYPE,
32 )
33 from homeassistant.core import Event, EventStateChangedData, callback
34 from homeassistant.exceptions import HomeAssistantError
35 from homeassistant.helpers import (
36  config_validation as cv,
37  device_registry as dr,
38  entity_registry as er,
39 )
40 from homeassistant.helpers.event import async_track_state_change_event
41 from homeassistant.helpers.typing import VolDictType
42 
43 from . import (
44  DOMAIN,
45  DeviceTuple,
46  get_device_id,
47  get_device_tuple_from_identifiers,
48  get_rfx_object,
49 )
50 from .binary_sensor import supported as binary_supported
51 from .const import (
52  CONF_AUTOMATIC_ADD,
53  CONF_DATA_BITS,
54  CONF_OFF_DELAY,
55  CONF_PROTOCOLS,
56  CONF_REPLACE_DEVICE,
57  CONF_VENETIAN_BLIND_MODE,
58  CONST_VENETIAN_BLIND_MODE_DEFAULT,
59  CONST_VENETIAN_BLIND_MODE_EU,
60  CONST_VENETIAN_BLIND_MODE_US,
61  DEVICE_PACKET_TYPE_LIGHTING4,
62 )
63 
64 CONF_EVENT_CODE = "event_code"
65 CONF_MANUAL_PATH = "Enter Manually"
66 
67 RECV_MODES = sorted(itertools.chain(*rfxtrxmod.lowlevel.Status.RECMODES))
68 
69 
70 class DeviceData(TypedDict):
71  """Dict data representing a device entry."""
72 
73  event_code: str | None
74  device_id: DeviceTuple
75 
76 
77 def none_or_int(value: str | None, base: int) -> int | None:
78  """Check if string is one otherwise convert to int."""
79  if value is None:
80  return None
81  return int(value, base)
82 
83 
85  """Handle Rfxtrx options."""
86 
87  _device_registry: dr.DeviceRegistry
88  _device_entries: list[dr.DeviceEntry]
89 
90  def __init__(self) -> None:
91  """Initialize rfxtrx options flow."""
92  self._global_options_global_options: dict[str, Any] = {}
93  self._selected_device_selected_device: dict[str, Any] = {}
94  self._selected_device_entry_id_selected_device_entry_id: str | None = None
95  self._selected_device_event_code_selected_device_event_code: str | None = None
96  self._selected_device_object_selected_device_object: rfxtrxmod.RFXtrxEvent | None = None
97 
98  async def async_step_init(
99  self, user_input: dict[str, Any] | None = None
100  ) -> ConfigFlowResult:
101  """Manage the options."""
102  return await self.async_step_prompt_optionsasync_step_prompt_options()
103 
105  self, user_input: dict[str, Any] | None = None
106  ) -> ConfigFlowResult:
107  """Prompt for options."""
108  errors = {}
109 
110  if user_input is not None:
111  self._global_options_global_options = {
112  CONF_AUTOMATIC_ADD: user_input[CONF_AUTOMATIC_ADD],
113  CONF_PROTOCOLS: user_input[CONF_PROTOCOLS] or None,
114  }
115  if CONF_DEVICE in user_input:
116  entry_id = user_input[CONF_DEVICE]
117  device_data = self._get_device_data_get_device_data(entry_id)
118  self._selected_device_entry_id_selected_device_entry_id = entry_id
119  event_code = device_data["event_code"]
120  assert event_code
121  self._selected_device_event_code_selected_device_event_code = event_code
122  self._selected_device_selected_device = self.config_entryconfig_entryconfig_entry.data[CONF_DEVICES][event_code]
123  self._selected_device_object_selected_device_object = get_rfx_object(event_code)
124  return await self.async_step_set_device_optionsasync_step_set_device_options()
125  if CONF_EVENT_CODE in user_input:
126  self._selected_device_event_code_selected_device_event_code = cast(
127  str, user_input[CONF_EVENT_CODE]
128  )
129  self._selected_device_selected_device = {}
130  selected_device_object = get_rfx_object(
131  self._selected_device_event_code_selected_device_event_code
132  )
133  if selected_device_object is None:
134  errors[CONF_EVENT_CODE] = "invalid_event_code"
135  elif not self._can_add_device_can_add_device(selected_device_object):
136  errors[CONF_EVENT_CODE] = "already_configured_device"
137  else:
138  self._selected_device_object_selected_device_object = selected_device_object
139  return await self.async_step_set_device_optionsasync_step_set_device_options()
140 
141  if not errors:
142  self.update_config_dataupdate_config_data(global_options=self._global_options_global_options)
143 
144  return self.async_create_entryasync_create_entry(title="", data={})
145 
146  device_registry = dr.async_get(self.hass)
147  device_entries = dr.async_entries_for_config_entry(
148  device_registry, self.config_entryconfig_entryconfig_entry.entry_id
149  )
150  self._device_registry_device_registry = device_registry
151  self._device_entries_device_entries = device_entries
152 
153  configure_devices = {
154  entry.id: entry.name_by_user if entry.name_by_user else entry.name
155  for entry in device_entries
156  if self._get_device_event_code_get_device_event_code(entry.id) is not None
157  }
158 
159  options = {
160  vol.Optional(
161  CONF_AUTOMATIC_ADD,
162  default=self.config_entryconfig_entryconfig_entry.data[CONF_AUTOMATIC_ADD],
163  ): bool,
164  vol.Optional(
165  CONF_PROTOCOLS,
166  default=self.config_entryconfig_entryconfig_entry.data.get(CONF_PROTOCOLS) or [],
167  ): cv.multi_select(RECV_MODES),
168  vol.Optional(CONF_EVENT_CODE): str,
169  vol.Optional(CONF_DEVICE): vol.In(configure_devices),
170  }
171 
172  return self.async_show_formasync_show_form(
173  step_id="prompt_options", data_schema=vol.Schema(options), errors=errors
174  )
175 
177  self, user_input: dict[str, Any] | None = None
178  ) -> ConfigFlowResult:
179  """Manage device options."""
180  errors = {}
181  assert self._selected_device_object_selected_device_object
182  assert self._selected_device_event_code_selected_device_event_code
183 
184  if user_input is not None:
185  devices: dict[str, dict[str, Any] | None] = {}
186  device: dict[str, Any]
187  device_id = get_device_id(
188  self._selected_device_object_selected_device_object.device,
189  data_bits=user_input.get(CONF_DATA_BITS),
190  )
191 
192  if CONF_REPLACE_DEVICE in user_input:
193  await self._async_replace_device_async_replace_device(user_input[CONF_REPLACE_DEVICE])
194 
195  devices = {self._selected_device_event_code_selected_device_event_code: None}
196  self.update_config_dataupdate_config_data(
197  global_options=self._global_options_global_options, devices=devices
198  )
199 
200  return self.async_create_entryasync_create_entry(title="", data={})
201 
202  try:
203  command_on = none_or_int(user_input.get(CONF_COMMAND_ON), 16)
204  except ValueError:
205  errors[CONF_COMMAND_ON] = "invalid_input_2262_on"
206 
207  try:
208  command_off = none_or_int(user_input.get(CONF_COMMAND_OFF), 16)
209  except ValueError:
210  errors[CONF_COMMAND_OFF] = "invalid_input_2262_off"
211 
212  try:
213  off_delay = none_or_int(user_input.get(CONF_OFF_DELAY), 10)
214  except ValueError:
215  errors[CONF_OFF_DELAY] = "invalid_input_off_delay"
216 
217  if not errors:
218  devices = {}
219  device = {
220  CONF_DEVICE_ID: list(device_id),
221  }
222 
223  devices[self._selected_device_event_code_selected_device_event_code] = device
224 
225  if off_delay:
226  device[CONF_OFF_DELAY] = off_delay
227  if user_input.get(CONF_DATA_BITS):
228  device[CONF_DATA_BITS] = user_input[CONF_DATA_BITS]
229  if command_on:
230  device[CONF_COMMAND_ON] = command_on
231  if command_off:
232  device[CONF_COMMAND_OFF] = command_off
233  if user_input.get(CONF_VENETIAN_BLIND_MODE):
234  device[CONF_VENETIAN_BLIND_MODE] = user_input[
235  CONF_VENETIAN_BLIND_MODE
236  ]
237 
238  self.update_config_dataupdate_config_data(
239  global_options=self._global_options_global_options, devices=devices
240  )
241 
242  return self.async_create_entryasync_create_entry(title="", data={})
243 
244  device_data = self._selected_device_selected_device
245 
246  data_schema: VolDictType = {}
247 
248  if binary_supported(self._selected_device_object_selected_device_object):
249  off_delay_schema: VolDictType
250  if device_data.get(CONF_OFF_DELAY):
251  off_delay_schema = {
252  vol.Optional(
253  CONF_OFF_DELAY,
254  description={"suggested_value": device_data[CONF_OFF_DELAY]},
255  ): str,
256  }
257  else:
258  off_delay_schema = {
259  vol.Optional(CONF_OFF_DELAY): str,
260  }
261  data_schema.update(off_delay_schema)
262 
263  if (
264  self._selected_device_object_selected_device_object.device.packettype
265  == DEVICE_PACKET_TYPE_LIGHTING4
266  ):
267  data_schema.update(
268  {
269  vol.Optional(
270  CONF_DATA_BITS, default=device_data.get(CONF_DATA_BITS, 0)
271  ): int,
272  vol.Optional(
273  CONF_COMMAND_ON,
274  default=hex(device_data.get(CONF_COMMAND_ON, 0)),
275  ): str,
276  vol.Optional(
277  CONF_COMMAND_OFF,
278  default=hex(device_data.get(CONF_COMMAND_OFF, 0)),
279  ): str,
280  }
281  )
282 
283  if isinstance(self._selected_device_object_selected_device_object.device, rfxtrxmod.RfyDevice):
284  data_schema.update(
285  {
286  vol.Optional(
287  CONF_VENETIAN_BLIND_MODE,
288  default=device_data.get(
289  CONF_VENETIAN_BLIND_MODE, CONST_VENETIAN_BLIND_MODE_DEFAULT
290  ),
291  ): vol.In(
292  [
293  CONST_VENETIAN_BLIND_MODE_DEFAULT,
294  CONST_VENETIAN_BLIND_MODE_US,
295  CONST_VENETIAN_BLIND_MODE_EU,
296  ]
297  ),
298  }
299  )
300  replace_devices = {
301  entry.id: entry.name_by_user if entry.name_by_user else entry.name
302  for entry in self._device_entries_device_entries
303  if self._can_replace_device_can_replace_device(entry.id)
304  }
305 
306  if replace_devices:
307  data_schema.update(
308  {
309  vol.Optional(CONF_REPLACE_DEVICE): vol.In(replace_devices),
310  }
311  )
312 
313  return self.async_show_formasync_show_form(
314  step_id="set_device_options",
315  data_schema=vol.Schema(data_schema),
316  errors=errors,
317  )
318 
319  async def _async_replace_device(self, replace_device: str) -> None:
320  """Migrate properties of a device into another."""
321  device_registry = self._device_registry_device_registry
322  old_device = self._selected_device_entry_id_selected_device_entry_id
323  assert old_device
324  old_entry = device_registry.async_get(old_device)
325  assert old_entry
326  device_registry.async_update_device(
327  replace_device,
328  area_id=old_entry.area_id,
329  name_by_user=old_entry.name_by_user,
330  )
331 
332  old_device_data = self._get_device_data_get_device_data(old_device)
333  new_device_data = self._get_device_data_get_device_data(replace_device)
334 
335  old_device_id = "_".join(x for x in old_device_data[CONF_DEVICE_ID])
336  new_device_id = "_".join(x for x in new_device_data[CONF_DEVICE_ID])
337 
338  entity_registry = er.async_get(self.hass)
339  entity_entries = er.async_entries_for_device(
340  entity_registry, old_device, include_disabled_entities=True
341  )
342  entity_migration_map = {}
343  for entry in entity_entries:
344  unique_id = entry.unique_id
345  new_unique_id = unique_id.replace(old_device_id, new_device_id)
346 
347  new_entity_id = entity_registry.async_get_entity_id(
348  entry.domain, entry.platform, new_unique_id
349  )
350 
351  if new_entity_id is not None:
352  entity_migration_map[new_entity_id] = entry
353 
354  @callback
355  def _handle_state_removed(event: Event[EventStateChangedData]) -> None:
356  # Wait for entities to finish cleanup
357  new_state = event.data["new_state"]
358  entity_id = event.data["entity_id"]
359  if new_state is None and entity_id in entities_to_be_removed:
360  entities_to_be_removed.remove(entity_id)
361  if not entities_to_be_removed:
362  wait_for_entities.set()
363 
364  # Create a set with entities to be removed which are currently in the state
365  # machine
366  entities_to_be_removed = {
367  entry.entity_id
368  for entry in entity_migration_map.values()
369  if not self.hass.states.async_available(entry.entity_id)
370  }
371  wait_for_entities = asyncio.Event()
372  remove_track_state_changes = async_track_state_change_event(
373  self.hass, entities_to_be_removed, _handle_state_removed
374  )
375 
376  for entry in entity_migration_map.values():
377  entity_registry.async_remove(entry.entity_id)
378 
379  # Wait for entities to finish cleanup
380  with suppress(TimeoutError):
381  async with asyncio.timeout(10):
382  await wait_for_entities.wait()
383  remove_track_state_changes()
384 
385  @callback
386  def _handle_state_added(event: Event[EventStateChangedData]) -> None:
387  # Wait for entities to be added
388  old_state = event.data["old_state"]
389  entity_id = event.data["entity_id"]
390  if old_state is None and entity_id in entities_to_be_added:
391  entities_to_be_added.remove(entity_id)
392  if not entities_to_be_added:
393  wait_for_entities.set()
394 
395  # Create a set with entities to be added to the state machine
396  entities_to_be_added = {
397  entry.entity_id
398  for entry in entity_migration_map.values()
399  if self.hass.states.async_available(entry.entity_id)
400  }
401  wait_for_entities = asyncio.Event()
402  remove_track_state_changes = async_track_state_change_event(
403  self.hass, entities_to_be_added, _handle_state_added
404  )
405 
406  for entity_id, entry in entity_migration_map.items():
407  entity_registry.async_update_entity(
408  entity_id,
409  new_entity_id=entry.entity_id,
410  name=entry.name,
411  icon=entry.icon,
412  )
413 
414  # Wait for entities to finish renaming
415  with suppress(TimeoutError):
416  async with asyncio.timeout(10):
417  await wait_for_entities.wait()
418  remove_track_state_changes()
419 
420  device_registry.async_remove_device(old_device)
421 
422  def _can_add_device(self, new_rfx_obj: rfxtrxmod.RFXtrxEvent) -> bool:
423  """Check if device does not already exist."""
424  new_device_id = get_device_id(new_rfx_obj.device)
425  for packet_id, entity_info in self.config_entryconfig_entryconfig_entry.data[CONF_DEVICES].items():
426  rfx_obj = get_rfx_object(packet_id)
427  assert rfx_obj
428 
429  device_id = get_device_id(rfx_obj.device, entity_info.get(CONF_DATA_BITS))
430  if new_device_id == device_id:
431  return False
432 
433  return True
434 
435  def _can_replace_device(self, entry_id: str) -> bool:
436  """Check if device can be replaced with selected device."""
437  assert self._selected_device_object_selected_device_object
438 
439  device_data = self._get_device_data_get_device_data(entry_id)
440 
441  if (event_code := device_data["event_code"]) is not None:
442  rfx_obj = get_rfx_object(event_code)
443  assert rfx_obj
444 
445  if (
446  rfx_obj.device.packettype
447  == self._selected_device_object_selected_device_object.device.packettype
448  and rfx_obj.device.subtype
449  == self._selected_device_object_selected_device_object.device.subtype
450  and self._selected_device_event_code_selected_device_event_code != event_code
451  ):
452  return True
453 
454  return False
455 
456  def _get_device_event_code(self, entry_id: str) -> str | None:
457  data = self._get_device_data_get_device_data(entry_id)
458 
459  return data["event_code"]
460 
461  def _get_device_data(self, entry_id: str) -> DeviceData:
462  """Get event code based on device identifier."""
463  event_code: str | None = None
464  entry = self._device_registry_device_registry.async_get(entry_id)
465  assert entry
466  device_id = get_device_tuple_from_identifiers(entry.identifiers)
467  assert device_id
468  for packet_id, entity_info in self.config_entryconfig_entryconfig_entry.data[CONF_DEVICES].items():
469  if tuple(entity_info.get(CONF_DEVICE_ID)) == device_id:
470  event_code = cast(str, packet_id)
471  break
472  return DeviceData(event_code=event_code, device_id=device_id)
473 
474  @callback
476  self,
477  global_options: dict[str, Any] | None = None,
478  devices: dict[str, Any] | None = None,
479  ) -> None:
480  """Update data in ConfigEntry."""
481  entry_data = self.config_entryconfig_entryconfig_entry.data.copy()
482  entry_data[CONF_DEVICES] = copy.deepcopy(self.config_entryconfig_entryconfig_entry.data[CONF_DEVICES])
483  if global_options:
484  entry_data.update(global_options)
485  if devices:
486  for event_code, options in devices.items():
487  if options is None:
488  # If the config entry is setup, the device registry
489  # listener will remove the device from the config
490  # entry before we get here
491  entry_data[CONF_DEVICES].pop(event_code, None)
492  else:
493  entry_data[CONF_DEVICES][event_code] = options
494  self.hass.config_entries.async_update_entry(self.config_entryconfig_entryconfig_entry, data=entry_data)
495  self.hass.async_create_task(
496  self.hass.config_entries.async_reload(self.config_entryconfig_entryconfig_entry.entry_id)
497  )
498 
499 
500 class RfxtrxConfigFlow(ConfigFlow, domain=DOMAIN):
501  """Handle a config flow for RFXCOM RFXtrx."""
502 
503  VERSION = 1
504 
505  async def async_step_user(
506  self, user_input: dict[str, Any] | None = None
507  ) -> ConfigFlowResult:
508  """Step when user initializes a integration."""
509  await self.async_set_unique_idasync_set_unique_id(DOMAIN)
510  self._abort_if_unique_id_configured_abort_if_unique_id_configured()
511 
512  errors: dict[str, str] = {}
513  if user_input is not None:
514  if user_input[CONF_TYPE] == "Serial":
515  return await self.async_step_setup_serialasync_step_setup_serial()
516 
517  return await self.async_step_setup_networkasync_step_setup_network()
518 
519  list_of_types = ["Serial", "Network"]
520 
521  schema = vol.Schema({vol.Required(CONF_TYPE): vol.In(list_of_types)})
522  return self.async_show_formasync_show_formasync_show_form(step_id="user", data_schema=schema, errors=errors)
523 
525  self, user_input: dict[str, Any] | None = None
526  ) -> ConfigFlowResult:
527  """Step when setting up network configuration."""
528  errors: dict[str, str] = {}
529 
530  if user_input is not None:
531  host = user_input[CONF_HOST]
532  port = user_input[CONF_PORT]
533 
534  try:
535  data = await self.async_validate_rfxasync_validate_rfx(host=host, port=port)
536  except CannotConnect:
537  errors["base"] = "cannot_connect"
538 
539  if not errors:
540  return self.async_create_entryasync_create_entryasync_create_entry(title="RFXTRX", data=data)
541 
542  schema = vol.Schema(
543  {vol.Required(CONF_HOST): str, vol.Required(CONF_PORT): int}
544  )
545  return self.async_show_formasync_show_formasync_show_form(
546  step_id="setup_network",
547  data_schema=schema,
548  errors=errors,
549  )
550 
552  self, user_input: dict[str, Any] | None = None
553  ) -> ConfigFlowResult:
554  """Step when setting up serial configuration."""
555  errors: dict[str, str] = {}
556 
557  if user_input is not None:
558  user_selection = user_input[CONF_DEVICE]
559  if user_selection == CONF_MANUAL_PATH:
560  return await self.async_step_setup_serial_manual_pathasync_step_setup_serial_manual_path()
561 
562  dev_path = await self.hass.async_add_executor_job(
563  get_serial_by_id, user_selection
564  )
565 
566  try:
567  data = await self.async_validate_rfxasync_validate_rfx(device=dev_path)
568  except CannotConnect:
569  errors["base"] = "cannot_connect"
570 
571  if not errors:
572  return self.async_create_entryasync_create_entryasync_create_entry(title="RFXTRX", data=data)
573 
574  ports = await self.hass.async_add_executor_job(serial.tools.list_ports.comports)
575  list_of_ports = {}
576  for port in ports:
577  list_of_ports[port.device] = (
578  f"{port}, s/n: {port.serial_number or 'n/a'}"
579  + (f" - {port.manufacturer}" if port.manufacturer else "")
580  )
581  list_of_ports[CONF_MANUAL_PATH] = CONF_MANUAL_PATH
582 
583  schema = vol.Schema({vol.Required(CONF_DEVICE): vol.In(list_of_ports)})
584  return self.async_show_formasync_show_formasync_show_form(
585  step_id="setup_serial",
586  data_schema=schema,
587  errors=errors,
588  )
589 
591  self, user_input: dict[str, Any] | None = None
592  ) -> ConfigFlowResult:
593  """Select path manually."""
594  errors: dict[str, str] = {}
595 
596  if user_input is not None:
597  device = user_input[CONF_DEVICE]
598  try:
599  data = await self.async_validate_rfxasync_validate_rfx(device=device)
600  except CannotConnect:
601  errors["base"] = "cannot_connect"
602 
603  if not errors:
604  return self.async_create_entryasync_create_entryasync_create_entry(title="RFXTRX", data=data)
605 
606  schema = vol.Schema({vol.Required(CONF_DEVICE): str})
607  return self.async_show_formasync_show_formasync_show_form(
608  step_id="setup_serial_manual_path",
609  data_schema=schema,
610  errors=errors,
611  )
612 
614  self,
615  host: str | None = None,
616  port: int | None = None,
617  device: str | None = None,
618  ) -> dict[str, Any]:
619  """Create data for rfxtrx entry."""
620  success = await self.hass.async_add_executor_job(
621  _test_transport, host, port, device
622  )
623  if not success:
624  raise CannotConnect
625 
626  data: dict[str, Any] = {
627  CONF_HOST: host,
628  CONF_PORT: port,
629  CONF_DEVICE: device,
630  CONF_AUTOMATIC_ADD: False,
631  CONF_DEVICES: {},
632  }
633  return data
634 
635  @staticmethod
636  @callback
638  config_entry: ConfigEntry,
639  ) -> RfxtrxOptionsFlow:
640  """Get the options flow for this handler."""
641  return RfxtrxOptionsFlow()
642 
643 
644 def _test_transport(host: str | None, port: int | None, device: str | None) -> bool:
645  """Construct a rfx object based on config."""
646  if port is not None:
647  conn = rfxtrxmod.PyNetworkTransport((host, port))
648  else:
649  conn = rfxtrxmod.PySerialTransport(device)
650 
651  try:
652  conn.connect()
653  except (rfxtrxmod.RFXtrxTransportError, TimeoutError):
654  return False
655 
656  return True
657 
658 
659 def get_serial_by_id(dev_path: str) -> str:
660  """Return a /dev/serial/by-id match for given device if available."""
661  by_id = "/dev/serial/by-id"
662  if not os.path.isdir(by_id):
663  return dev_path
664 
665  for path in (entry.path for entry in os.scandir(by_id) if entry.is_symlink()):
666  if os.path.realpath(path) == dev_path:
667  return path
668  return dev_path
669 
670 
672  """Error to indicate we cannot connect."""
ConfigFlowResult async_step_setup_serial(self, dict[str, Any]|None user_input=None)
Definition: config_flow.py:553
RfxtrxOptionsFlow async_get_options_flow(ConfigEntry config_entry)
Definition: config_flow.py:639
ConfigFlowResult async_step_setup_network(self, dict[str, Any]|None user_input=None)
Definition: config_flow.py:526
dict[str, Any] async_validate_rfx(self, str|None host=None, int|None port=None, str|None device=None)
Definition: config_flow.py:618
ConfigFlowResult async_step_user(self, dict[str, Any]|None user_input=None)
Definition: config_flow.py:507
ConfigFlowResult async_step_setup_serial_manual_path(self, dict[str, Any]|None user_input=None)
Definition: config_flow.py:592
None update_config_data(self, dict[str, Any]|None global_options=None, dict[str, Any]|None devices=None)
Definition: config_flow.py:479
ConfigFlowResult async_step_init(self, dict[str, Any]|None user_input=None)
Definition: config_flow.py:100
bool _can_add_device(self, rfxtrxmod.RFXtrxEvent new_rfx_obj)
Definition: config_flow.py:422
ConfigFlowResult async_step_set_device_options(self, dict[str, Any]|None user_input=None)
Definition: config_flow.py:178
ConfigFlowResult async_step_prompt_options(self, dict[str, Any]|None user_input=None)
Definition: config_flow.py:106
None _abort_if_unique_id_configured(self, dict[str, Any]|None updates=None, bool reload_on_update=True, *str error="already_configured")
ConfigEntry|None async_set_unique_id(self, str|None unique_id=None, *bool raise_on_progress=True)
ConfigFlowResult async_create_entry(self, *str title, Mapping[str, Any] data, str|None description=None, Mapping[str, str]|None description_placeholders=None, Mapping[str, Any]|None options=None)
ConfigFlowResult async_show_form(self, *str|None step_id=None, vol.Schema|None data_schema=None, dict[str, str]|None errors=None, Mapping[str, str]|None description_placeholders=None, bool|None last_step=None, str|None preview=None)
None config_entry(self, ConfigEntry value)
_FlowResultT async_show_form(self, *str|None step_id=None, vol.Schema|None data_schema=None, dict[str, str]|None errors=None, Mapping[str, str]|None description_placeholders=None, bool|None last_step=None, str|None preview=None)
_FlowResultT async_create_entry(self, *str|None title=None, Mapping[str, Any] data, str|None description=None, Mapping[str, str]|None description_placeholders=None)
bool _test_transport(str|None host, int|None port, str|None device)
Definition: config_flow.py:644
int|None none_or_int(str|None value, int base)
Definition: config_flow.py:77
DeviceTuple|None get_device_tuple_from_identifiers(set[tuple[str, str]] identifiers)
Definition: __init__.py:449
DeviceTuple get_device_id(rfxtrxmod.RFXtrxDevice device, int|None data_bits=None)
Definition: __init__.py:434
rfxtrxmod.RFXtrxEvent|None get_rfx_object(str packetid)
Definition: __init__.py:352
AreaRegistry async_get(HomeAssistant hass)
CALLBACK_TYPE async_track_state_change_event(HomeAssistant hass, str|Iterable[str] entity_ids, Callable[[Event[EventStateChangedData]], Any] action, HassJobType|None job_type=None)
Definition: event.py:314