Home Assistant Unofficial Reference 2024.12.1
config_flow.py
Go to the documentation of this file.
1 """Config flow to configure Motionblinds using their WLAN API."""
2 
3 from __future__ import annotations
4 
5 from typing import Any
6 
7 from motionblinds import MotionDiscovery, MotionGateway
8 import voluptuous as vol
9 
10 from homeassistant.components import dhcp
11 from homeassistant.config_entries import (
12  ConfigEntry,
13  ConfigFlow,
14  ConfigFlowResult,
15  OptionsFlow,
16 )
17 from homeassistant.const import CONF_API_KEY, CONF_HOST
18 from homeassistant.core import callback
19 from homeassistant.helpers.device_registry import format_mac
20 
21 from .const import (
22  CONF_INTERFACE,
23  CONF_WAIT_FOR_PUSH,
24  DEFAULT_GATEWAY_NAME,
25  DEFAULT_INTERFACE,
26  DEFAULT_WAIT_FOR_PUSH,
27  DOMAIN,
28 )
29 from .gateway import ConnectMotionGateway
30 
31 CONFIG_SCHEMA = vol.Schema(
32  {
33  vol.Optional(CONF_HOST): str,
34  }
35 )
36 
37 
39  """Options for the component."""
40 
41  async def async_step_init(
42  self, user_input: dict[str, Any] | None = None
43  ) -> ConfigFlowResult:
44  """Manage the options."""
45  errors: dict[str, str] = {}
46  if user_input is not None:
47  return self.async_create_entryasync_create_entry(title="", data=user_input)
48 
49  settings_schema = vol.Schema(
50  {
51  vol.Optional(
52  CONF_WAIT_FOR_PUSH,
53  default=self.config_entryconfig_entryconfig_entry.options.get(
54  CONF_WAIT_FOR_PUSH, DEFAULT_WAIT_FOR_PUSH
55  ),
56  ): bool,
57  }
58  )
59 
60  return self.async_show_formasync_show_form(
61  step_id="init", data_schema=settings_schema, errors=errors
62  )
63 
64 
65 class MotionBlindsFlowHandler(ConfigFlow, domain=DOMAIN):
66  """Handle a Motionblinds config flow."""
67 
68  VERSION = 1
69 
70  def __init__(self) -> None:
71  """Initialize the Motionblinds flow."""
72  self._host_host: str | None = None
73  self._ips_ips: list[str] = []
74  self._config_settings_config_settings: vol.Schema | None = None
75 
76  @staticmethod
77  @callback
79  config_entry: ConfigEntry,
80  ) -> OptionsFlowHandler:
81  """Get the options flow."""
82  return OptionsFlowHandler()
83 
84  async def async_step_dhcp(
85  self, discovery_info: dhcp.DhcpServiceInfo
86  ) -> ConfigFlowResult:
87  """Handle discovery via dhcp."""
88  mac_address = format_mac(discovery_info.macaddress).replace(":", "")
89  await self.async_set_unique_idasync_set_unique_id(mac_address)
90  self._abort_if_unique_id_configured_abort_if_unique_id_configured(updates={CONF_HOST: discovery_info.ip})
91 
92  gateway = MotionGateway(ip=discovery_info.ip, key="abcd1234-56ef-78")
93  try:
94  # key not needed for GetDeviceList request
95  await self.hass.async_add_executor_job(gateway.GetDeviceList)
96  except Exception: # noqa: BLE001
97  return self.async_abortasync_abortasync_abort(reason="not_motionblinds")
98 
99  if not gateway.available:
100  return self.async_abortasync_abortasync_abort(reason="not_motionblinds")
101 
102  short_mac = mac_address[-6:].upper()
103  self.context["title_placeholders"] = {
104  "short_mac": short_mac,
105  "ip_address": discovery_info.ip,
106  }
107 
108  self._host_host = discovery_info.ip
109  return await self.async_step_connectasync_step_connect()
110 
111  async def async_step_user(
112  self, user_input: dict[str, Any] | None = None
113  ) -> ConfigFlowResult:
114  """Handle a flow initialized by the user."""
115  errors = {}
116  if user_input is not None:
117  self._host_host = user_input.get(CONF_HOST)
118 
119  if self._host_host is not None:
120  return await self.async_step_connectasync_step_connect()
121 
122  # Use MotionGateway discovery
123  discover_class = MotionDiscovery()
124  gateways = await self.hass.async_add_executor_job(discover_class.discover)
125  self._ips_ips = list(gateways)
126 
127  if len(self._ips_ips) == 1:
128  self._host_host = self._ips_ips[0]
129  return await self.async_step_connectasync_step_connect()
130 
131  if len(self._ips_ips) > 1:
132  return await self.async_step_selectasync_step_select()
133 
134  errors["base"] = "discovery_error"
135 
136  return self.async_show_formasync_show_formasync_show_form(
137  step_id="user", data_schema=CONFIG_SCHEMA, errors=errors
138  )
139 
140  async def async_step_select(
141  self, user_input: dict[str, Any] | None = None
142  ) -> ConfigFlowResult:
143  """Handle multiple motion gateways found."""
144  if user_input is not None:
145  self._host_host = user_input["select_ip"]
146  return await self.async_step_connectasync_step_connect()
147 
148  select_schema = vol.Schema({vol.Required("select_ip"): vol.In(self._ips_ips)})
149 
150  return self.async_show_formasync_show_formasync_show_form(step_id="select", data_schema=select_schema)
151 
153  self, user_input: dict[str, Any] | None = None
154  ) -> ConfigFlowResult:
155  """Connect to the Motion Gateway."""
156  errors: dict[str, str] = {}
157  if user_input is not None:
158  key = user_input[CONF_API_KEY]
159 
160  connect_gateway_class = ConnectMotionGateway(self.hass)
161  if not await connect_gateway_class.async_connect_gateway(self._host_host, key):
162  return self.async_abortasync_abortasync_abort(reason="connection_error")
163  motion_gateway = connect_gateway_class.gateway_device
164 
165  # check socket interface
166  check_multicast_class = ConnectMotionGateway(
167  self.hass, interface=DEFAULT_INTERFACE
168  )
169  multicast_interface = await check_multicast_class.async_check_interface(
170  self._host_host, key
171  )
172 
173  mac_address = motion_gateway.mac
174 
175  await self.async_set_unique_idasync_set_unique_id(mac_address, raise_on_progress=False)
176  self._abort_if_unique_id_configured_abort_if_unique_id_configured(
177  updates={
178  CONF_HOST: self._host_host,
179  CONF_API_KEY: key,
180  CONF_INTERFACE: multicast_interface,
181  }
182  )
183 
184  return self.async_create_entryasync_create_entryasync_create_entry(
185  title=DEFAULT_GATEWAY_NAME,
186  data={
187  CONF_HOST: self._host_host,
188  CONF_API_KEY: key,
189  CONF_INTERFACE: multicast_interface,
190  },
191  )
192 
193  self._config_settings_config_settings = vol.Schema(
194  {
195  vol.Required(CONF_API_KEY): vol.All(str, vol.Length(min=16, max=16)),
196  }
197  )
198 
199  return self.async_show_formasync_show_formasync_show_form(
200  step_id="connect", data_schema=self._config_settings_config_settings, errors=errors
201  )
ConfigFlowResult async_step_user(self, dict[str, Any]|None user_input=None)
Definition: config_flow.py:113
ConfigFlowResult async_step_select(self, dict[str, Any]|None user_input=None)
Definition: config_flow.py:142
ConfigFlowResult async_step_dhcp(self, dhcp.DhcpServiceInfo discovery_info)
Definition: config_flow.py:86
ConfigFlowResult async_step_connect(self, dict[str, Any]|None user_input=None)
Definition: config_flow.py:154
OptionsFlowHandler async_get_options_flow(ConfigEntry config_entry)
Definition: config_flow.py:80
ConfigFlowResult async_step_init(self, dict[str, Any]|None user_input=None)
Definition: config_flow.py:43
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)
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)
_FlowResultT async_abort(self, *str reason, Mapping[str, str]|None description_placeholders=None)