1 """Config flow to configure the Android Debug Bridge integration."""
3 from __future__
import annotations
9 from androidtv
import state_detection_rules_validator
10 import voluptuous
as vol
29 from .
import async_connect_androidtv, get_androidtv_mac
35 CONF_EXCLUDE_UNNAMED_APPS,
37 CONF_SCREENCAP_INTERVAL,
38 CONF_STATE_DETECTION_RULES,
39 CONF_TURN_OFF_COMMAND,
41 DEFAULT_ADB_SERVER_PORT,
43 DEFAULT_EXCLUDE_UNNAMED_APPS,
46 DEFAULT_SCREENCAP_INTERVAL,
53 APPS_NEW_ID =
"NewApp"
54 CONF_APP_DELETE =
"app_delete"
55 CONF_APP_ID =
"app_id"
56 CONF_APP_NAME =
"app_name"
58 RULES_NEW_ID =
"NewRule"
59 CONF_RULE_DELETE =
"rule_delete"
60 CONF_RULE_ID =
"rule_id"
61 CONF_RULE_VALUES =
"rule_values"
63 RESULT_CONN_ERROR =
"cannot_connect"
64 RESULT_UNKNOWN =
"unknown"
66 _LOGGER = logging.getLogger(__name__)
70 """Validate that the value is an existing file."""
71 file_in = os.path.expanduser(value)
72 return os.path.isfile(file_in)
and os.access(file_in, os.R_OK)
76 """Handle a config flow."""
84 user_input: dict[str, Any] |
None =
None,
85 error: str |
None =
None,
86 ) -> ConfigFlowResult:
87 """Show the setup form to the user."""
88 host = user_input.get(CONF_HOST,
"")
if user_input
else ""
89 data_schema = vol.Schema(
91 vol.Required(CONF_HOST, default=host): str,
92 vol.Required(CONF_DEVICE_CLASS, default=DEFAULT_DEVICE_CLASS): vol.In(
95 vol.Required(CONF_PORT, default=DEFAULT_PORT): cv.port,
100 data_schema = data_schema.extend(
102 vol.Optional(CONF_ADBKEY): str,
103 vol.Optional(CONF_ADB_SERVER_IP): str,
105 CONF_ADB_SERVER_PORT, default=DEFAULT_ADB_SERVER_PORT
112 data_schema=data_schema,
113 errors={
"base": error}
if error
else None,
117 self, user_input: dict[str, Any]
118 ) -> tuple[str |
None, str |
None]:
119 """Attempt to connect the Android device."""
125 "Unknown error connecting with Android device at %s",
126 user_input[CONF_HOST],
128 return RESULT_UNKNOWN,
None
131 _LOGGER.warning(error_message)
132 return RESULT_CONN_ERROR,
None
134 dev_prop = aftv.device_properties
136 "Android device at %s: %s = %r, %s = %r",
137 user_input[CONF_HOST],
139 dev_prop.get(PROP_ETHMAC),
141 dev_prop.get(PROP_WIFIMAC),
144 await aftv.adb_close()
145 return None, unique_id
148 self, user_input: dict[str, Any] |
None =
None
149 ) -> ConfigFlowResult:
150 """Handle a flow initiated by the user."""
153 if user_input
is not None:
154 host = user_input[CONF_HOST]
155 adb_key = user_input.get(CONF_ADBKEY)
156 if CONF_ADB_SERVER_IP
in user_input:
160 user_input.pop(CONF_ADB_SERVER_PORT,
None)
163 if not await self.hass.async_add_executor_job(_is_file, adb_key):
185 """Get the options flow for this handler."""
190 """Handle an option flow for Android Debug Bridge."""
192 def __init__(self, config_entry: ConfigEntry) ->
None:
193 """Initialize options flow."""
194 self._apps: dict[str, Any] =
dict(config_entry.options.get(CONF_APPS, {}))
195 self._state_det_rules: dict[str, Any] =
dict(
196 config_entry.options.get(CONF_STATE_DETECTION_RULES, {})
203 """Save the updated options."""
206 for k, v
in data.items()
207 if k
not in [CONF_APPS, CONF_STATE_DETECTION_RULES]
210 new_data[CONF_APPS] = self._apps
211 if self._state_det_rules:
212 new_data[CONF_STATE_DETECTION_RULES] = self._state_det_rules
217 self, user_input: dict[str, Any] |
None =
None
218 ) -> ConfigFlowResult:
219 """Handle options flow."""
220 if user_input
is not None:
221 if sel_app := user_input.get(CONF_APPS):
223 if sel_rule := user_input.get(CONF_STATE_DETECTION_RULES):
231 """Return initial configuration form."""
233 apps_list = {k: f
"{v} ({k})" if v
else k
for k, v
in self._apps.items()}
237 rules = [RULES_NEW_ID, *self._state_det_rules]
240 data_schema = vol.Schema(
247 default=options.get(CONF_GET_SOURCES, DEFAULT_GET_SOURCES),
250 CONF_EXCLUDE_UNNAMED_APPS,
252 CONF_EXCLUDE_UNNAMED_APPS, DEFAULT_EXCLUDE_UNNAMED_APPS
256 CONF_SCREENCAP_INTERVAL,
258 CONF_SCREENCAP_INTERVAL, DEFAULT_SCREENCAP_INTERVAL
260 ): vol.All(vol.Coerce(int), vol.Clamp(min=0, max=15)),
262 CONF_TURN_OFF_COMMAND,
264 "suggested_value": options.get(CONF_TURN_OFF_COMMAND,
"")
268 CONF_TURN_ON_COMMAND,
270 "suggested_value": options.get(CONF_TURN_ON_COMMAND,
"")
275 options=rules, mode=SelectSelectorMode.DROPDOWN
281 return self.
async_show_formasync_show_form(step_id=
"init", data_schema=data_schema)
284 self, user_input: dict[str, Any] |
None =
None, app_id: str |
None =
None
285 ) -> ConfigFlowResult:
286 """Handle options flow for apps list."""
287 if app_id
is not None:
288 self.
_conf_app_id_conf_app_id = app_id
if app_id != APPS_NEW_ID
else None
291 if user_input
is not None:
292 app_id = user_input.get(CONF_APP_ID, self.
_conf_app_id_conf_app_id)
294 if user_input.get(CONF_APP_DELETE,
False):
295 self._apps.pop(app_id)
297 self._apps[app_id] = user_input.get(CONF_APP_NAME,
"")
303 """Return configuration form for apps."""
307 description={
"suggested_value": self._apps.
get(app_id,
"")},
310 if app_id == APPS_NEW_ID:
311 data_schema = vol.Schema({**app_schema, vol.Optional(CONF_APP_ID): str})
313 data_schema = vol.Schema(
314 {**app_schema, vol.Optional(CONF_APP_DELETE, default=
False): bool}
319 data_schema=data_schema,
320 description_placeholders={
321 "app_id": f
"`{app_id}`" if app_id != APPS_NEW_ID
else "",
326 self, user_input: dict[str, Any] |
None =
None, rule_id: str |
None =
None
327 ) -> ConfigFlowResult:
328 """Handle options flow for detection rules."""
329 if rule_id
is not None:
330 self.
_conf_rule_id_conf_rule_id = rule_id
if rule_id != RULES_NEW_ID
else None
333 if user_input
is not None:
334 rule_id = user_input.get(CONF_RULE_ID, self.
_conf_rule_id_conf_rule_id)
336 if user_input.get(CONF_RULE_DELETE,
False):
337 self._state_det_rules.pop(rule_id)
338 elif det_rule := user_input.get(CONF_RULE_VALUES):
340 if state_det_rule
is None:
344 errors={
"base":
"invalid_det_rules"},
346 self._state_det_rules[rule_id] = state_det_rule
352 self, rule_id: str, default_id: str =
"", errors: dict[str, str] |
None =
None
353 ) -> ConfigFlowResult:
354 """Return configuration form for detection rules."""
357 CONF_RULE_VALUES, default=self._state_det_rules.
get(rule_id)
360 if rule_id == RULES_NEW_ID:
361 data_schema = vol.Schema(
362 {vol.Optional(CONF_RULE_ID, default=default_id): str, **rule_schema}
365 data_schema = vol.Schema(
366 {**rule_schema, vol.Optional(CONF_RULE_DELETE, default=
False): bool}
371 data_schema=data_schema,
372 description_placeholders={
373 "rule_id": f
"`{rule_id}`" if rule_id != RULES_NEW_ID
else "",
380 """Validate a string that contain state detection rules and return a dict."""
381 json_rules = state_det_rules
382 if not isinstance(json_rules, list):
383 json_rules = [json_rules]
386 state_detection_rules_validator(json_rules, ValueError)
387 except ValueError
as exc:
388 _LOGGER.warning(
"Invalid state detection rules: %s", exc)
tuple[str|None, str|None] _async_check_connection(self, dict[str, Any] user_input)
ConfigFlowResult _show_setup_form(self, dict[str, Any]|None user_input=None, str|None error=None)
ConfigFlowResult async_step_user(self, dict[str, Any]|None user_input=None)
OptionsFlowHandler async_get_options_flow(ConfigEntry config_entry)
ConfigFlowResult _save_config(self, dict[str, Any] data)
ConfigFlowResult _async_init_form(self)
ConfigFlowResult _async_apps_form(self, str app_id)
ConfigFlowResult _async_rules_form(self, str rule_id, str default_id="", dict[str, str]|None errors=None)
None __init__(self, ConfigEntry config_entry)
ConfigFlowResult async_step_rules(self, dict[str, Any]|None user_input=None, str|None rule_id=None)
ConfigFlowResult async_step_init(self, dict[str, Any]|None user_input=None)
ConfigFlowResult async_step_apps(self, dict[str, Any]|None user_input=None, str|None app_id=None)
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_abort(self, *str reason, Mapping[str, str]|None description_placeholders=None)
None _async_abort_entries_match(self, dict[str, Any]|None match_dict=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)
ConfigEntry config_entry(self)
None config_entry(self, ConfigEntry value)
bool show_advanced_options(self)
_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)
_FlowResultT async_abort(self, *str reason, Mapping[str, str]|None description_placeholders=None)
list[Any]|None _validate_state_det_rules(Any state_det_rules)
tuple[AndroidTVAsync|FireTVAsync|None, str|None] async_connect_androidtv(HomeAssistant hass, Mapping[str, Any] config, *dict[str, Any]|None state_detection_rules=None, float timeout=30.0)
str|None get_androidtv_mac(dict[str, Any] dev_props)
web.Response get(self, web.Request request, str config_key)