1 """Config flow to configure Axis devices."""
3 from __future__
import annotations
5 from collections.abc
import Mapping
6 from ipaddress
import ip_address
7 from types
import MappingProxyType
9 from urllib.parse
import urlsplit
11 import voluptuous
as vol
38 from .
import AxisConfigEntry
42 DEFAULT_STREAM_PROFILE,
44 DOMAIN
as AXIS_DOMAIN,
46 from .errors
import AuthenticationRequired, CannotConnect
47 from .hub
import AxisHub, get_axis_api
49 AXIS_OUI = {
"00:40:8c",
"ac:cc:8e",
"b8:a4:4f"}
51 DEFAULT_PROTOCOL =
"https"
52 PROTOCOL_CHOICES = [
"https",
"http"]
56 """Handle a Axis config flow."""
63 config_entry: ConfigEntry,
64 ) -> AxisOptionsFlowHandler:
65 """Get the options flow for this handler."""
69 """Initialize the Axis config flow."""
70 self.
configconfig: dict[str, Any] = {}
74 self, user_input: dict[str, Any] |
None =
None
75 ) -> ConfigFlowResult:
76 """Handle a Axis config flow start.
78 Manage device specific parameters.
82 if user_input
is not None:
84 api = await
get_axis_api(self.hass, MappingProxyType(user_input))
86 except AuthenticationRequired:
87 errors[
"base"] =
"invalid_auth"
90 errors[
"base"] =
"cannot_connect"
93 serial = api.vapix.serial_number
95 CONF_PROTOCOL: user_input[CONF_PROTOCOL],
96 CONF_HOST: user_input[CONF_HOST],
97 CONF_PORT: user_input[CONF_PORT],
98 CONF_USERNAME: user_input[CONF_USERNAME],
99 CONF_PASSWORD: user_input[CONF_PASSWORD],
116 self.
configconfig = config | {CONF_MODEL: api.vapix.product_number}
121 vol.Required(CONF_PROTOCOL): vol.In(PROTOCOL_CHOICES),
122 vol.Required(CONF_HOST): str,
123 vol.Required(CONF_USERNAME): str,
124 vol.Required(CONF_PASSWORD): str,
125 vol.Required(CONF_PORT, default=DEFAULT_PORT): int,
130 description_placeholders=self.
configconfig,
131 data_schema=vol.Schema(data),
136 """Create entry for device.
138 Generate a name to be used as a prefix for device entities.
140 model = self.
configconfig[CONF_MODEL]
142 entry.data[CONF_NAME]
143 for entry
in self.hass.config_entries.async_entries(AXIS_DOMAIN)
144 if entry.source != SOURCE_IGNORE
and entry.data[CONF_MODEL] == model
148 for idx
in range(len(same_model) + 1):
149 name = f
"{model} {idx}"
150 if name
not in same_model:
153 self.
configconfig[CONF_NAME] = name
155 title = f
"{model} - {serial}"
159 self, user_input: dict[str, Any] |
None =
None
160 ) -> ConfigFlowResult:
161 """Trigger a reconfiguration flow."""
167 self, entry_data: Mapping[str, Any]
168 ) -> ConfigFlowResult:
169 """Trigger a reauthentication flow."""
170 self.context[
"title_placeholders"] = {
171 CONF_NAME: entry_data[CONF_NAME],
172 CONF_HOST: entry_data[CONF_HOST],
177 self, entry_data: Mapping[str, Any], keep_password: bool
178 ) -> ConfigFlowResult:
179 """Re-run configuration step."""
180 protocol = entry_data.get(CONF_PROTOCOL,
"http")
181 password = entry_data[CONF_PASSWORD]
if keep_password
else ""
183 vol.Required(CONF_PROTOCOL, default=protocol): vol.In(PROTOCOL_CHOICES),
184 vol.Required(CONF_HOST, default=entry_data[CONF_HOST]): str,
185 vol.Required(CONF_USERNAME, default=entry_data[CONF_USERNAME]): str,
186 vol.Required(CONF_PASSWORD, default=password): str,
187 vol.Required(CONF_PORT, default=entry_data[CONF_PORT]): int,
193 self, discovery_info: dhcp.DhcpServiceInfo
194 ) -> ConfigFlowResult:
195 """Prepare configuration for a DHCP discovered Axis device."""
198 CONF_HOST: discovery_info.ip,
199 CONF_MAC:
format_mac(discovery_info.macaddress),
200 CONF_NAME: discovery_info.hostname,
206 self, discovery_info: ssdp.SsdpServiceInfo
207 ) -> ConfigFlowResult:
208 """Prepare configuration for a SSDP discovered Axis device."""
209 url = urlsplit(discovery_info.upnp[ssdp.ATTR_UPNP_PRESENTATION_URL])
212 CONF_HOST: url.hostname,
213 CONF_MAC:
format_mac(discovery_info.upnp[ssdp.ATTR_UPNP_SERIAL]),
214 CONF_NAME: f
"{discovery_info.upnp[ssdp.ATTR_UPNP_FRIENDLY_NAME]}",
220 self, discovery_info: zeroconf.ZeroconfServiceInfo
221 ) -> ConfigFlowResult:
222 """Prepare configuration for a Zeroconf discovered Axis device."""
225 CONF_HOST: discovery_info.host,
226 CONF_MAC:
format_mac(discovery_info.properties[
"macaddress"]),
227 CONF_NAME: discovery_info.name.split(
".", 1)[0],
228 CONF_PORT: discovery_info.port,
233 self, discovery_info: dict[str, Any]
234 ) -> ConfigFlowResult:
235 """Prepare configuration for a discovered Axis device."""
236 if discovery_info[CONF_MAC][:8]
not in AXIS_OUI:
245 updates={CONF_HOST: discovery_info[CONF_HOST]}
250 "title_placeholders": {
251 CONF_NAME: discovery_info[CONF_NAME],
252 CONF_HOST: discovery_info[CONF_HOST],
254 "configuration_url": f
"http://{discovery_info[CONF_HOST]}:{discovery_info[CONF_PORT]}",
259 vol.Required(CONF_PROTOCOL): vol.In(PROTOCOL_CHOICES),
260 vol.Required(CONF_HOST, default=discovery_info[CONF_HOST]): str,
261 vol.Required(CONF_USERNAME): str,
262 vol.Required(CONF_PASSWORD): str,
263 vol.Required(CONF_PORT, default=DEFAULT_PORT): int,
270 """Handle Axis device options."""
272 config_entry: AxisConfigEntry
276 self, user_input: dict[str, Any] |
None =
None
277 ) -> ConfigFlowResult:
278 """Manage the Axis device options."""
283 self, user_input: dict[str, Any] |
None =
None
284 ) -> ConfigFlowResult:
285 """Manage the Axis device stream options."""
286 if user_input
is not None:
291 vapix = self.
hubhub.api.vapix
295 if vapix.stream_profiles
or (
296 (profiles := vapix.params.stream_profile_handler.get(
"0"))
297 and profiles.max_groups > 0
299 stream_profiles = [DEFAULT_STREAM_PROFILE]
300 stream_profiles.extend(profile.name
for profile
in vapix.streaming_profiles)
304 CONF_STREAM_PROFILE, default=self.
hubhub.config.stream_profile
306 ] = vol.In(stream_profiles)
311 properties := vapix.params.property_handler.get(
"0")
312 )
and properties.image_number_of_views > 0:
313 await vapix.params.image_handler.update()
314 video_sources: dict[int | str, str] = {
315 DEFAULT_VIDEO_SOURCE: DEFAULT_VIDEO_SOURCE
317 for idx, video_source
in vapix.params.image_handler.items():
318 if not video_source.enabled:
320 video_sources[
int(idx) + 1] = video_source.name
323 vol.Optional(CONF_VIDEO_SOURCE, default=self.
hubhub.config.video_source)
324 ] = vol.In(video_sources)
327 step_id=
"configure_stream", data_schema=vol.Schema(schema)
ConfigFlowResult _redo_configuration(self, Mapping[str, Any] entry_data, bool keep_password)
ConfigFlowResult async_step_user(self, dict[str, Any]|None user_input=None)
ConfigFlowResult async_step_reconfigure(self, dict[str, Any]|None user_input=None)
ConfigFlowResult _process_discovered_device(self, dict[str, Any] discovery_info)
ConfigFlowResult async_step_reauth(self, Mapping[str, Any] entry_data)
ConfigFlowResult async_step_dhcp(self, dhcp.DhcpServiceInfo discovery_info)
AxisOptionsFlowHandler async_get_options_flow(ConfigEntry config_entry)
ConfigFlowResult _create_entry(self, str serial)
ConfigFlowResult async_step_ssdp(self, ssdp.SsdpServiceInfo discovery_info)
ConfigFlowResult async_step_zeroconf(self, zeroconf.ZeroconfServiceInfo discovery_info)
ConfigFlowResult async_step_init(self, dict[str, Any]|None user_input=None)
ConfigFlowResult async_step_configure_stream(self, dict[str, Any]|None user_input=None)
None _abort_if_unique_id_configured(self, dict[str, Any]|None updates=None, bool reload_on_update=True, *str error="already_configured")
ConfigEntry _get_reauth_entry(self)
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_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)
ConfigFlowResult async_abort(self, *str reason, Mapping[str, str]|None description_placeholders=None)
ConfigEntry _get_reconfigure_entry(self)
None _abort_if_unique_id_mismatch(self, *str reason="unique_id_mismatch", 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)
ConfigEntry config_entry(self)
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)
axis.AxisDevice get_axis_api(HomeAssistant hass, MappingProxyType[str, Any] config)
IssData update(pyiss.ISS iss)
bool is_link_local(IPv4Address|IPv6Address address)