5050 HTTP_DIGEST_AUTHENTICATION ,
5151)
5252from homeassistant .core import HomeAssistant
53+ from homeassistant .data_entry_flow import section
5354from homeassistant .exceptions import HomeAssistantError , TemplateError
5455from homeassistant .helpers import config_validation as cv , template as template_helper
5556from homeassistant .helpers .entity_platform import PlatformData
5657from homeassistant .helpers .httpx_client import get_async_client
58+ from homeassistant .helpers .selector import (
59+ SelectOptionDict ,
60+ SelectSelector ,
61+ SelectSelectorConfig ,
62+ SelectSelectorMode ,
63+ )
5764from homeassistant .util import slugify
5865
5966from .camera import GenericCamera , generate_auth
6774 DEFAULT_NAME ,
6875 DOMAIN ,
6976 GET_IMAGE_TIMEOUT ,
77+ SECTION_ADVANCED ,
7078)
7179
7280_LOGGER = logging .getLogger (__name__ )
7381
7482DEFAULT_DATA = {
7583 CONF_NAME : DEFAULT_NAME ,
76- CONF_AUTHENTICATION : HTTP_BASIC_AUTHENTICATION ,
77- CONF_LIMIT_REFETCH_TO_URL_CHANGE : False ,
78- CONF_FRAMERATE : 2 ,
79- CONF_VERIFY_SSL : True ,
80- CONF_RTSP_TRANSPORT : "tcp" ,
84+ SECTION_ADVANCED : {
85+ CONF_AUTHENTICATION : HTTP_BASIC_AUTHENTICATION ,
86+ CONF_LIMIT_REFETCH_TO_URL_CHANGE : False ,
87+ CONF_FRAMERATE : 2 ,
88+ CONF_VERIFY_SSL : True ,
89+ CONF_RTSP_TRANSPORT : "tcp" ,
90+ },
8191}
8292
8393SUPPORTED_IMAGE_TYPES = {"png" , "jpeg" , "gif" , "svg+xml" , "webp" }
@@ -94,58 +104,47 @@ def __init__(self, error: str, details: str | None = None) -> None:
94104
95105
96106def build_schema (
97- user_input : Mapping [str , Any ],
98107 is_options_flow : bool = False ,
99108 show_advanced_options : bool = False ,
100109) -> vol .Schema :
101110 """Create schema for camera config setup."""
111+ rtsp_options = [
112+ SelectOptionDict (
113+ value = value ,
114+ label = name ,
115+ )
116+ for value , name in RTSP_TRANSPORTS .items ()
117+ ]
118+
119+ advanced_section = {
120+ vol .Required (CONF_FRAMERATE ): vol .All (
121+ vol .Range (min = 0 , min_included = False ), cv .positive_float
122+ ),
123+ vol .Required (CONF_VERIFY_SSL ): bool ,
124+ vol .Optional (CONF_RTSP_TRANSPORT ): SelectSelector (
125+ SelectSelectorConfig (
126+ options = rtsp_options ,
127+ mode = SelectSelectorMode .DROPDOWN ,
128+ )
129+ ),
130+ vol .Optional (CONF_AUTHENTICATION ): vol .In (
131+ [HTTP_BASIC_AUTHENTICATION , HTTP_DIGEST_AUTHENTICATION ]
132+ ),
133+ }
102134 spec = {
103- vol .Optional (
104- CONF_STILL_IMAGE_URL ,
105- description = {"suggested_value" : user_input .get (CONF_STILL_IMAGE_URL , "" )},
106- ): str ,
107- vol .Optional (
108- CONF_STREAM_SOURCE ,
109- description = {"suggested_value" : user_input .get (CONF_STREAM_SOURCE , "" )},
110- ): str ,
111- vol .Optional (
112- CONF_RTSP_TRANSPORT ,
113- description = {"suggested_value" : user_input .get (CONF_RTSP_TRANSPORT )},
114- ): vol .In (RTSP_TRANSPORTS ),
115- vol .Optional (
116- CONF_AUTHENTICATION ,
117- description = {"suggested_value" : user_input .get (CONF_AUTHENTICATION )},
118- ): vol .In ([HTTP_BASIC_AUTHENTICATION , HTTP_DIGEST_AUTHENTICATION ]),
119- vol .Optional (
120- CONF_USERNAME ,
121- description = {"suggested_value" : user_input .get (CONF_USERNAME , "" )},
122- ): str ,
123- vol .Optional (
124- CONF_PASSWORD ,
125- description = {"suggested_value" : user_input .get (CONF_PASSWORD , "" )},
126- ): str ,
127- vol .Required (
128- CONF_FRAMERATE ,
129- description = {"suggested_value" : user_input .get (CONF_FRAMERATE , 2 )},
130- ): vol .All (vol .Range (min = 0 , min_included = False ), cv .positive_float ),
131- vol .Required (
132- CONF_VERIFY_SSL , default = user_input .get (CONF_VERIFY_SSL , True )
133- ): bool ,
135+ vol .Optional (CONF_STREAM_SOURCE ): str ,
136+ vol .Optional (CONF_STILL_IMAGE_URL ): str ,
137+ vol .Optional (CONF_USERNAME ): str ,
138+ vol .Optional (CONF_PASSWORD ): str ,
139+ vol .Required (SECTION_ADVANCED ): section (
140+ vol .Schema (advanced_section ), {"collapsed" : True }
141+ ),
134142 }
135143 if is_options_flow :
136- spec [
137- vol .Required (
138- CONF_LIMIT_REFETCH_TO_URL_CHANGE ,
139- default = user_input .get (CONF_LIMIT_REFETCH_TO_URL_CHANGE , False ),
140- )
141- ] = bool
144+ advanced_section [vol .Optional (CONF_LIMIT_REFETCH_TO_URL_CHANGE )] = bool
142145 if show_advanced_options :
143- spec [
144- vol .Required (
145- CONF_USE_WALLCLOCK_AS_TIMESTAMPS ,
146- default = user_input .get (CONF_USE_WALLCLOCK_AS_TIMESTAMPS , False ),
147- )
148- ] = bool
146+ advanced_section [vol .Optional (CONF_USE_WALLCLOCK_AS_TIMESTAMPS )] = bool
147+
149148 return vol .Schema (spec )
150149
151150
@@ -187,7 +186,7 @@ async def async_test_still(
187186 return {CONF_STILL_IMAGE_URL : "malformed_url" }, None
188187 if not yarl_url .is_absolute ():
189188 return {CONF_STILL_IMAGE_URL : "relative_url" }, None
190- verify_ssl = info [CONF_VERIFY_SSL ]
189+ verify_ssl = info [SECTION_ADVANCED ][ CONF_VERIFY_SSL ]
191190 auth = generate_auth (info )
192191 try :
193192 async_client = get_async_client (hass , verify_ssl = verify_ssl )
@@ -268,9 +267,9 @@ async def async_test_and_preview_stream(
268267 _LOGGER .warning ("Problem rendering template %s: %s" , stream_source , err )
269268 raise InvalidStreamException ("template_error" ) from err
270269 stream_options : dict [str , str | bool | float ] = {}
271- if rtsp_transport := info .get (CONF_RTSP_TRANSPORT ):
270+ if rtsp_transport := info [ SECTION_ADVANCED ] .get (CONF_RTSP_TRANSPORT ):
272271 stream_options [CONF_RTSP_TRANSPORT ] = rtsp_transport
273- if info .get (CONF_USE_WALLCLOCK_AS_TIMESTAMPS ):
272+ if info [ SECTION_ADVANCED ] .get (CONF_USE_WALLCLOCK_AS_TIMESTAMPS ):
274273 stream_options [CONF_USE_WALLCLOCK_AS_TIMESTAMPS ] = True
275274
276275 try :
@@ -326,7 +325,7 @@ def register_still_preview(hass: HomeAssistant) -> None:
326325class GenericIPCamConfigFlow (ConfigFlow , domain = DOMAIN ):
327326 """Config flow for generic IP camera."""
328327
329- VERSION = 1
328+ VERSION = 2
330329
331330 def __init__ (self ) -> None :
332331 """Initialize Generic ConfigFlow."""
@@ -381,7 +380,7 @@ async def async_step_user(
381380 user_input = DEFAULT_DATA .copy ()
382381 return self .async_show_form (
383382 step_id = "user" ,
384- data_schema = build_schema (user_input ),
383+ data_schema = self . add_suggested_values_to_schema ( build_schema (), user_input ),
385384 errors = errors ,
386385 )
387386
@@ -449,13 +448,19 @@ async def async_step_init(
449448 self .preview_stream = None
450449 if not errors :
451450 data = {
452- CONF_USE_WALLCLOCK_AS_TIMESTAMPS : self .config_entry .options .get (
453- CONF_USE_WALLCLOCK_AS_TIMESTAMPS , False
454- ),
455451 ** user_input ,
456452 CONF_CONTENT_TYPE : still_format
457453 or self .config_entry .options .get (CONF_CONTENT_TYPE ),
458454 }
455+ if (
456+ CONF_USE_WALLCLOCK_AS_TIMESTAMPS
457+ not in user_input [SECTION_ADVANCED ]
458+ ):
459+ data [SECTION_ADVANCED ][CONF_USE_WALLCLOCK_AS_TIMESTAMPS ] = (
460+ self .config_entry .options [SECTION_ADVANCED ].get (
461+ CONF_USE_WALLCLOCK_AS_TIMESTAMPS , False
462+ )
463+ )
459464 self .user_input = data
460465 # temporary preview for user to check the image
461466 self .preview_image_settings = data
@@ -464,10 +469,12 @@ async def async_step_init(
464469 user_input = self .user_input
465470 return self .async_show_form (
466471 step_id = "init" ,
467- data_schema = build_schema (
472+ data_schema = self .add_suggested_values_to_schema (
473+ build_schema (
474+ True ,
475+ self .show_advanced_options ,
476+ ),
468477 user_input or self .config_entry .options ,
469- True ,
470- self .show_advanced_options ,
471478 ),
472479 errors = errors ,
473480 )
0 commit comments