1 """Lutron Homeworks Series 4 and 8 config flow."""
3 from __future__
import annotations
5 from functools
import partial
9 from pyhomeworks
import exceptions
as hw_exceptions
10 from pyhomeworks.pyhomeworks
import Homeworks
11 import voluptuous
as vol
27 config_validation
as cv,
28 entity_registry
as er,
32 SchemaCommonFlowHandler,
36 SchemaOptionsFlowHandler,
58 from .util
import calculate_unique_id
60 _LOGGER = logging.getLogger(__name__)
62 DEFAULT_FADE_RATE = 1.0
65 vol.Required(CONF_HOST): selector.TextSelector(),
66 vol.Required(CONF_PORT): selector.NumberSelector(
67 selector.NumberSelectorConfig(
70 mode=selector.NumberSelectorMode.BOX,
73 vol.Optional(CONF_USERNAME): selector.TextSelector(),
74 vol.Optional(CONF_PASSWORD): selector.TextSelector(
75 selector.TextSelectorConfig(type=selector.TextSelectorType.PASSWORD)
79 LIGHT_EDIT: VolDictType = {
80 vol.Optional(CONF_RATE, default=DEFAULT_FADE_RATE): selector.NumberSelector(
81 selector.NumberSelectorConfig(
84 mode=selector.NumberSelectorMode.SLIDER,
90 BUTTON_EDIT: VolDictType = {
91 vol.Optional(CONF_LED, default=
False): selector.BooleanSelector(),
92 vol.Optional(CONF_RELEASE_DELAY, default=0): selector.NumberSelector(
93 selector.NumberSelectorConfig(
97 mode=selector.NumberSelectorMode.BOX,
98 unit_of_measurement=
"s",
104 validate_addr = cv.matches_regex(
r"\[(?:\d\d:){2,4}\d\d\]")
108 """Validate credentials."""
109 if CONF_PASSWORD
in user_input
and CONF_USERNAME
not in user_input:
114 handler: ConfigFlow | SchemaOptionsFlowHandler, user_input: dict[str, Any]
116 """Validate controller setup."""
118 user_input[CONF_CONTROLLER_ID] =
slugify(user_input[CONF_NAME])
119 user_input[CONF_PORT] =
int(user_input[CONF_PORT])
121 handler._async_abort_entries_match(
122 {CONF_HOST: user_input[CONF_HOST], CONF_PORT: user_input[CONF_PORT]}
124 except AbortFlow
as err:
128 handler._async_abort_entries_match(
129 {CONF_CONTROLLER_ID: user_input[CONF_CONTROLLER_ID]}
131 except AbortFlow
as err:
140 """Try connecting to the controller."""
142 def _try_connect(host: str, port: int) ->
None:
143 """Try connecting to the controller.
145 Raises ConnectionError if the connection fails.
148 "Trying to connect to %s:%s", user_input[CONF_HOST], user_input[CONF_PORT]
150 controller = Homeworks(
153 lambda msg_types, values:
None,
154 user_input.get(CONF_USERNAME),
155 user_input.get(CONF_PASSWORD),
162 await hass.async_add_executor_job(
163 _try_connect, user_input[CONF_HOST], user_input[CONF_PORT]
165 except hw_exceptions.HomeworksConnectionFailed
as err:
166 _LOGGER.debug(
"Caught HomeworksConnectionFailed")
168 except hw_exceptions.HomeworksInvalidCredentialsProvided
as err:
169 _LOGGER.debug(
"Caught HomeworksInvalidCredentialsProvided")
171 except hw_exceptions.HomeworksNoCredentialsProvided
as err:
172 _LOGGER.debug(
"Caught HomeworksNoCredentialsProvided")
174 except Exception
as err:
175 _LOGGER.exception(
"Caught unexpected exception %s")
180 """Validate address."""
183 except vol.Invalid
as err:
186 for _key
in (CONF_DIMMERS, CONF_KEYPADS):
187 items: list[dict[str, Any]] = handler.options[_key]
190 if item[CONF_ADDR] == addr:
195 """Validate button number."""
196 keypad = handler.flow_state[
"_idx"]
197 buttons: list[dict[str, Any]] = handler.options[CONF_KEYPADS][keypad][CONF_BUTTONS]
199 for button
in buttons:
200 if button[CONF_NUMBER] == number:
205 handler: SchemaCommonFlowHandler, user_input: dict[str, Any]
207 """Validate button input."""
208 user_input[CONF_NUMBER] =
int(user_input[CONF_NUMBER])
213 keypad = handler.flow_state[
"_idx"]
214 buttons: list[dict[str, Any]] = handler.options[CONF_KEYPADS][keypad][CONF_BUTTONS]
215 buttons.append(user_input)
220 handler: SchemaCommonFlowHandler, user_input: dict[str, Any]
222 """Validate keypad or light input."""
227 items = handler.options[CONF_KEYPADS]
228 items.append(user_input | {CONF_BUTTONS: []})
233 handler: SchemaCommonFlowHandler, user_input: dict[str, Any]
235 """Validate light input."""
240 items = handler.options[CONF_DIMMERS]
241 items.append(user_input)
246 """Return schema for selecting a button."""
247 keypad = handler.flow_state[
"_idx"]
248 buttons: list[dict[str, Any]] = handler.options[CONF_KEYPADS][keypad][CONF_BUTTONS]
252 vol.Required(CONF_INDEX): vol.In(
254 str(index): f
"{config[CONF_NAME]} ({config[CONF_NUMBER]})"
255 for index, config
in enumerate(buttons)
263 """Return schema for selecting a keypad."""
266 vol.Required(CONF_INDEX): vol.In(
268 str(index): f
"{config[CONF_NAME]} ({config[CONF_ADDR]})"
269 for index, config
in enumerate(handler.options[CONF_KEYPADS])
277 """Return schema for selecting a light."""
280 vol.Required(CONF_INDEX): vol.In(
282 str(index): f
"{config[CONF_NAME]} ({config[CONF_ADDR]})"
283 for index, config
in enumerate(handler.options[CONF_DIMMERS])
291 handler: SchemaCommonFlowHandler, user_input: dict[str, Any]
293 """Store button index in flow state."""
294 handler.flow_state[
"_button_idx"] =
int(user_input[CONF_INDEX])
299 handler: SchemaCommonFlowHandler, user_input: dict[str, Any]
301 """Store keypad or light index in flow state."""
302 handler.flow_state[
"_idx"] =
int(user_input[CONF_INDEX])
307 handler: SchemaCommonFlowHandler,
309 """Return suggested values for button editing."""
310 keypad_idx: int = handler.flow_state[
"_idx"]
311 button_idx: int = handler.flow_state[
"_button_idx"]
312 return dict(handler.options[CONF_KEYPADS][keypad_idx][CONF_BUTTONS][button_idx])
316 handler: SchemaCommonFlowHandler,
318 """Return suggested values for light editing."""
319 idx: int = handler.flow_state[
"_idx"]
320 return dict(handler.options[CONF_DIMMERS][idx])
324 handler: SchemaCommonFlowHandler, user_input: dict[str, Any]
326 """Update edited keypad or light."""
329 keypad_idx: int = handler.flow_state[
"_idx"]
330 button_idx: int = handler.flow_state[
"_button_idx"]
331 buttons: list[dict] = handler.options[CONF_KEYPADS][keypad_idx][CONF_BUTTONS]
332 buttons[button_idx].
update(user_input)
337 handler: SchemaCommonFlowHandler, user_input: dict[str, Any]
339 """Update edited keypad or light."""
342 idx: int = handler.flow_state[
"_idx"]
343 handler.options[CONF_DIMMERS][idx].
update(user_input)
348 """Return schema for button removal."""
349 keypad_idx: int = handler.flow_state[
"_idx"]
350 buttons: list[dict] = handler.options[CONF_KEYPADS][keypad_idx][CONF_BUTTONS]
353 vol.Required(CONF_INDEX): cv.multi_select(
355 str(index): f
"{config[CONF_NAME]} ({config[CONF_NUMBER]})"
356 for index, config
in enumerate(buttons)
364 handler: SchemaCommonFlowHandler, *, key: str
366 """Return schema for keypad or light removal."""
369 vol.Required(CONF_INDEX): cv.multi_select(
371 str(index): f
"{config[CONF_NAME]} ({config[CONF_ADDR]})"
372 for index, config
in enumerate(handler.options[key])
380 handler: SchemaCommonFlowHandler, user_input: dict[str, Any]
382 """Validate remove keypad or light."""
383 removed_indexes: set[str] = set(user_input[CONF_INDEX])
387 entity_registry = er.async_get(handler.parent_handler.hass)
388 keypad_idx: int = handler.flow_state[
"_idx"]
389 keypad: dict = handler.options[CONF_KEYPADS][keypad_idx]
390 items: list[dict[str, Any]] = []
392 for index, item
in enumerate(keypad[CONF_BUTTONS]):
393 if str(index)
not in removed_indexes:
395 button_number = keypad[CONF_BUTTONS][index][CONF_NUMBER]
396 for domain
in (BINARY_SENSOR_DOMAIN, BUTTON_DOMAIN):
397 if entity_id := entity_registry.async_get_entity_id(
401 handler.options[CONF_CONTROLLER_ID],
406 entity_registry.async_remove(entity_id)
407 keypad[CONF_BUTTONS] = items
412 handler: SchemaCommonFlowHandler, user_input: dict[str, Any], *, key: str
414 """Validate remove keypad or light."""
415 removed_indexes: set[str] = set(user_input[CONF_INDEX])
419 entity_registry = er.async_get(handler.parent_handler.hass)
420 items: list[dict[str, Any]] = []
422 for index, item
in enumerate(handler.options[key]):
423 if str(index)
not in removed_indexes:
425 elif key != CONF_DIMMERS:
427 if entity_id := entity_registry.async_get_entity_id(
431 handler.options[CONF_CONTROLLER_ID], item[CONF_ADDR], 0
434 entity_registry.async_remove(entity_id)
435 handler.options[key] = items
439 DATA_SCHEMA_ADD_CONTROLLER = vol.Schema(
442 CONF_NAME, description={
"suggested_value":
"Lutron Homeworks"}
443 ): selector.TextSelector(),
447 DATA_SCHEMA_EDIT_CONTROLLER = vol.Schema(CONTROLLER_EDIT)
448 DATA_SCHEMA_ADD_LIGHT = vol.Schema(
450 vol.Optional(CONF_NAME, default=DEFAULT_LIGHT_NAME):
TextSelector(),
455 DATA_SCHEMA_ADD_KEYPAD = vol.Schema(
457 vol.Optional(CONF_NAME, default=DEFAULT_KEYPAD_NAME):
TextSelector(),
461 DATA_SCHEMA_ADD_BUTTON = vol.Schema(
463 vol.Optional(CONF_NAME, default=DEFAULT_BUTTON_NAME):
TextSelector(),
464 vol.Required(CONF_NUMBER): selector.NumberSelector(
465 selector.NumberSelectorConfig(
469 mode=selector.NumberSelectorMode.BOX,
475 DATA_SCHEMA_EDIT_BUTTON = vol.Schema(BUTTON_EDIT)
476 DATA_SCHEMA_EDIT_LIGHT = vol.Schema(LIGHT_EDIT)
482 "select_edit_keypad",
490 DATA_SCHEMA_ADD_KEYPAD,
491 suggested_values=
None,
492 validate_user_input=validate_add_keypad,
495 get_select_keypad_schema,
496 suggested_values=
None,
497 validate_user_input=validate_select_keypad_light,
498 next_step=
"edit_keypad",
503 "select_edit_button",
508 DATA_SCHEMA_ADD_BUTTON,
509 suggested_values=
None,
510 validate_user_input=validate_add_button,
513 get_select_button_schema,
514 suggested_values=
None,
515 validate_user_input=validate_select_button,
516 next_step=
"edit_button",
519 DATA_SCHEMA_EDIT_BUTTON,
520 suggested_values=get_edit_button_suggested_values,
521 validate_user_input=validate_button_edit,
524 get_remove_button_schema,
525 suggested_values=
None,
526 validate_user_input=validate_remove_button,
529 partial(get_remove_keypad_light_schema, key=CONF_KEYPADS),
530 suggested_values=
None,
531 validate_user_input=partial(validate_remove_keypad_light, key=CONF_KEYPADS),
534 DATA_SCHEMA_ADD_LIGHT,
535 suggested_values=
None,
536 validate_user_input=validate_add_light,
539 get_select_light_schema,
540 suggested_values=
None,
541 validate_user_input=validate_select_keypad_light,
542 next_step=
"edit_light",
545 DATA_SCHEMA_EDIT_LIGHT,
546 suggested_values=get_edit_light_suggested_values,
547 validate_user_input=validate_light_edit,
550 partial(get_remove_keypad_light_schema, key=CONF_DIMMERS),
551 suggested_values=
None,
552 validate_user_input=partial(validate_remove_keypad_light, key=CONF_DIMMERS),
558 """Config flow for Lutron Homeworks."""
561 self, user_input: dict[str, Any], reconfigure_entry: ConfigEntry
563 """Validate controller setup."""
565 user_input[CONF_PORT] =
int(user_input[CONF_PORT])
568 entry.entry_id != reconfigure_entry.entry_id
569 and user_input[CONF_HOST] == entry.options[CONF_HOST]
570 and user_input[CONF_PORT] == entry.options[CONF_PORT]
578 async
def async_step_reconfigure(
579 self, user_input: dict[str, Any] |
None =
None
580 ) -> ConfigFlowResult:
581 """Handle a reconfigure flow."""
585 CONF_HOST: reconfigure_entry.options[CONF_HOST],
586 CONF_PORT: reconfigure_entry.options[CONF_PORT],
587 CONF_USERNAME: reconfigure_entry.data.get(CONF_USERNAME),
588 CONF_PASSWORD: reconfigure_entry.data.get(CONF_PASSWORD),
593 CONF_HOST: user_input[CONF_HOST],
594 CONF_PORT: user_input[CONF_PORT],
595 CONF_USERNAME: user_input.get(CONF_USERNAME),
596 CONF_PASSWORD: user_input.get(CONF_PASSWORD),
600 except SchemaFlowError
as err:
601 errors[
"base"] =
str(err)
603 password = user_input.pop(CONF_PASSWORD,
None)
604 username = user_input.pop(CONF_USERNAME,
None)
605 new_data = reconfigure_entry.data | {
606 CONF_PASSWORD: password,
607 CONF_USERNAME: username,
609 new_options = reconfigure_entry.options | {
610 CONF_HOST: user_input[CONF_HOST],
611 CONF_PORT: user_input[CONF_PORT],
617 reload_even_if_entry_is_unchanged=
False,
621 step_id=
"reconfigure",
623 DATA_SCHEMA_EDIT_CONTROLLER, suggested_values
629 self, user_input: dict[str, Any] |
None =
None
630 ) -> ConfigFlowResult:
631 """Handle a flow initialized by the user."""
636 except SchemaFlowError
as err:
637 errors[
"base"] =
str(err)
640 {CONF_HOST: user_input[CONF_HOST], CONF_PORT: user_input[CONF_PORT]}
642 name = user_input.pop(CONF_NAME)
643 password = user_input.pop(CONF_PASSWORD,
None)
644 username = user_input.pop(CONF_USERNAME,
None)
645 user_input |= {CONF_DIMMERS: [], CONF_KEYPADS: []}
648 data={CONF_PASSWORD: password, CONF_USERNAME: username},
655 DATA_SCHEMA_ADD_CONTROLLER, user_input
663 """Options flow handler for Lutron Homeworks."""
dict[str, Any] _validate_edit_controller(self, dict[str, Any] user_input, ConfigEntry reconfigure_entry)
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)
list[ConfigEntry] _async_current_entries(self, bool|None include_ignore=None)
ConfigFlowResult async_update_reload_and_abort(self, ConfigEntry entry, *str|None|UndefinedType unique_id=UNDEFINED, str|UndefinedType title=UNDEFINED, Mapping[str, Any]|UndefinedType data=UNDEFINED, Mapping[str, Any]|UndefinedType data_updates=UNDEFINED, Mapping[str, Any]|UndefinedType options=UNDEFINED, str|UndefinedType reason=UNDEFINED, bool reload_even_if_entry_is_unchanged=True)
ConfigFlowResult async_step_user(self, dict[str, Any]|None user_input=None)
None _async_abort_entries_match(self, dict[str, Any]|None match_dict=None)
ConfigEntry _get_reconfigure_entry(self)
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)
OptionsFlow async_get_options_flow(ConfigEntry config_entry)
vol.Schema add_suggested_values_to_schema(self, vol.Schema data_schema, Mapping[str, Any]|None suggested_values)
_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)
None _try_connection(dict[str, Any] user_input)
vol.Schema get_remove_button_schema(SchemaCommonFlowHandler handler)
dict[str, Any] validate_select_keypad_light(SchemaCommonFlowHandler handler, dict[str, Any] user_input)
dict[str, Any] validate_button_edit(SchemaCommonFlowHandler handler, dict[str, Any] user_input)
dict[str, Any] validate_remove_keypad_light(SchemaCommonFlowHandler handler, dict[str, Any] user_input, *str key)
dict[str, Any] validate_light_edit(SchemaCommonFlowHandler handler, dict[str, Any] user_input)
dict[str, Any] validate_remove_button(SchemaCommonFlowHandler handler, dict[str, Any] user_input)
dict[str, Any] validate_add_light(SchemaCommonFlowHandler handler, dict[str, Any] user_input)
dict[str, Any] get_edit_button_suggested_values(SchemaCommonFlowHandler handler)
vol.Schema get_select_button_schema(SchemaCommonFlowHandler handler)
dict[str, Any] validate_add_keypad(SchemaCommonFlowHandler handler, dict[str, Any] user_input)
vol.Schema get_select_light_schema(SchemaCommonFlowHandler handler)
dict[str, Any] validate_select_button(SchemaCommonFlowHandler handler, dict[str, Any] user_input)
None _validate_credentials(dict[str, Any] user_input)
vol.Schema get_remove_keypad_light_schema(SchemaCommonFlowHandler handler, *str key)
None _validate_address(SchemaCommonFlowHandler handler, str addr)
dict[str, Any] get_edit_light_suggested_values(SchemaCommonFlowHandler handler)
None _validate_button_number(SchemaCommonFlowHandler handler, int number)
vol.Schema get_select_keypad_schema(SchemaCommonFlowHandler handler)
dict[str, Any] validate_add_button(SchemaCommonFlowHandler handler, dict[str, Any] user_input)
dict[str, Any] validate_add_controller(ConfigFlow|SchemaOptionsFlowHandler handler, dict[str, Any] user_input)
str calculate_unique_id(str controller_id, str addr, int idx)
IssData update(pyiss.ISS iss)
HomeAssistant async_get_hass()