@@ -94,7 +94,7 @@ async def async_step_user(
9494 ): SelectSelector (
9595 SelectSelectorConfig (
9696 options = SUPPORTED_AMP_TYPES ,
97- mode = SelectSelectorMode .DROPDOWN ,
97+ mode = SelectSelectorMode .LIST ,
9898 translation_key = 'amp_type' ,
9999 )
100100 ),
@@ -165,7 +165,9 @@ async def async_step_sources(
165165 data = self ._data ,
166166 )
167167
168- default_sources = '1: TV\n 2: Streaming\n 3: Turntable\n 4: CD Player'
168+ default_sources = self ._get_default_sources_text (
169+ self ._data .get (CONF_AMP_TYPE , DEFAULT_AMP_TYPE )
170+ )
169171
170172 return self .async_show_form (
171173 step_id = 'sources' ,
@@ -241,6 +243,17 @@ def _get_default_zones_text(self, amp_type: str) -> str:
241243 '14: Office\n 15: Patio\n 16: Dining Room'
242244 )
243245
246+ def _get_default_sources_text (self , amp_type : str ) -> str :
247+ """Get default sources text for an amplifier type."""
248+ # 8-source amps: xantech8, dax88
249+ if amp_type in ('xantech8' , 'dax88' ):
250+ return (
251+ '1: TV\n 2: Streaming\n 3: Turntable\n 4: CD Player\n '
252+ '5: Auxiliary\n 6: Tuner\n 7: Phono\n 8: Media Server'
253+ )
254+ # 6-source amps: monoprice6, zpr68-10, sonance6
255+ return '1: TV\n 2: Streaming\n 3: Turntable\n 4: CD Player\n 5: Auxiliary\n 6: Tuner'
256+
244257 @staticmethod
245258 @callback
246259 def async_get_options_flow (config_entry : ConfigEntry ) -> OptionsFlow :
@@ -256,10 +269,53 @@ def __init__(self, config_entry: ConfigEntry) -> None:
256269 self .config_entry = config_entry
257270
258271 async def async_step_init (
272+ self ,
273+ user_input : dict [str , Any ] | None = None , # noqa: ARG002
274+ ) -> ConfigFlowResult :
275+ """Show menu of configuration options."""
276+ return self .async_show_menu (
277+ step_id = 'init' ,
278+ menu_options = ['polling' , 'connection' , 'zones' , 'sources' ],
279+ )
280+
281+ async def async_step_connection (
282+ self ,
283+ user_input : dict [str , Any ] | None = None ,
284+ ) -> ConfigFlowResult :
285+ """Configure connection settings."""
286+ errors : dict [str , str ] = {}
287+
288+ if user_input is not None :
289+ new_port = user_input .get (CONF_PORT , '' )
290+ if not new_port :
291+ errors ['base' ] = 'cannot_connect'
292+ else :
293+ # update config entry data with new port
294+ new_data = {** self .config_entry .data , CONF_PORT : new_port }
295+ self .hass .config_entries .async_update_entry (
296+ self .config_entry , data = new_data
297+ )
298+ return self .async_create_entry (title = '' , data = self .config_entry .options )
299+
300+ current_port = self .config_entry .data .get (CONF_PORT , '/dev/ttyUSB0' )
301+
302+ return self .async_show_form (
303+ step_id = 'connection' ,
304+ data_schema = vol .Schema (
305+ {
306+ vol .Required (CONF_PORT , default = current_port ): TextSelector (
307+ TextSelectorConfig (type = TextSelectorType .TEXT )
308+ ),
309+ }
310+ ),
311+ errors = errors ,
312+ )
313+
314+ async def async_step_polling (
259315 self ,
260316 user_input : dict [str , Any ] | None = None ,
261317 ) -> ConfigFlowResult :
262- """Manage the options ."""
318+ """Configure polling interval ."""
263319 if user_input is not None :
264320 return self .async_create_entry (title = '' , data = user_input )
265321
@@ -268,7 +324,7 @@ async def async_step_init(
268324 )
269325
270326 return self .async_show_form (
271- step_id = 'init ' ,
327+ step_id = 'polling ' ,
272328 data_schema = vol .Schema (
273329 {
274330 vol .Optional (
@@ -286,3 +342,135 @@ async def async_step_init(
286342 }
287343 ),
288344 )
345+
346+ async def async_step_zones (
347+ self ,
348+ user_input : dict [str , Any ] | None = None ,
349+ ) -> ConfigFlowResult :
350+ """Configure zones."""
351+ errors : dict [str , str ] = {}
352+
353+ if user_input is not None :
354+ zones_text = user_input .get ('zones_config' , '' )
355+ zones = self ._parse_zones_config (zones_text )
356+
357+ if not zones :
358+ errors ['base' ] = 'invalid_zones'
359+ else :
360+ # update config entry data with new zones
361+ new_data = {** self .config_entry .data , CONF_ZONES : zones }
362+ self .hass .config_entries .async_update_entry (
363+ self .config_entry , data = new_data
364+ )
365+ return self .async_create_entry (title = '' , data = self .config_entry .options )
366+
367+ # convert current zones dict back to text
368+ current_zones = self .config_entry .data .get (CONF_ZONES , {})
369+ zones_text = self ._zones_to_text (current_zones )
370+
371+ return self .async_show_form (
372+ step_id = 'zones' ,
373+ data_schema = vol .Schema (
374+ {
375+ vol .Required ('zones_config' , default = zones_text ): TextSelector (
376+ TextSelectorConfig (
377+ type = TextSelectorType .TEXT ,
378+ multiline = True ,
379+ )
380+ ),
381+ }
382+ ),
383+ errors = errors ,
384+ description_placeholders = {
385+ 'example' : '11: Living Room\n 12: Kitchen\n 13: Bedroom' ,
386+ },
387+ )
388+
389+ async def async_step_sources (
390+ self ,
391+ user_input : dict [str , Any ] | None = None ,
392+ ) -> ConfigFlowResult :
393+ """Configure sources."""
394+ errors : dict [str , str ] = {}
395+
396+ if user_input is not None :
397+ sources_text = user_input .get ('sources_config' , '' )
398+ sources = self ._parse_sources_config (sources_text )
399+
400+ if not sources :
401+ errors ['base' ] = 'invalid_sources'
402+ else :
403+ # update config entry data with new sources
404+ new_data = {** self .config_entry .data , CONF_SOURCES : sources }
405+ self .hass .config_entries .async_update_entry (
406+ self .config_entry , data = new_data
407+ )
408+ return self .async_create_entry (title = '' , data = self .config_entry .options )
409+
410+ # convert current sources dict back to text
411+ current_sources = self .config_entry .data .get (CONF_SOURCES , {})
412+ sources_text = self ._sources_to_text (current_sources )
413+
414+ return self .async_show_form (
415+ step_id = 'sources' ,
416+ data_schema = vol .Schema (
417+ {
418+ vol .Required ('sources_config' , default = sources_text ): TextSelector (
419+ TextSelectorConfig (
420+ type = TextSelectorType .TEXT ,
421+ multiline = True ,
422+ )
423+ ),
424+ }
425+ ),
426+ errors = errors ,
427+ description_placeholders = {
428+ 'example' : '1: Sonos\n 2: Turntable\n 3: TV' ,
429+ },
430+ )
431+
432+ def _parse_zones_config (self , text : str ) -> dict [int , dict [str , str ]]:
433+ """Parse zone configuration text into structured dict."""
434+ zones : dict [int , dict [str , str ]] = {}
435+ for line in text .strip ().split ('\n ' ):
436+ line = line .strip ()
437+ if not line or ':' not in line :
438+ continue
439+ try :
440+ zone_id_str , name = line .split (':' , 1 )
441+ zone_id = int (zone_id_str .strip ())
442+ zones [zone_id ] = {'name' : name .strip ()}
443+ except ValueError :
444+ continue
445+ return zones
446+
447+ def _parse_sources_config (self , text : str ) -> dict [int , dict [str , str ]]:
448+ """Parse source configuration text into structured dict."""
449+ sources : dict [int , dict [str , str ]] = {}
450+ for line in text .strip ().split ('\n ' ):
451+ line = line .strip ()
452+ if not line or ':' not in line :
453+ continue
454+ try :
455+ source_id_str , name = line .split (':' , 1 )
456+ source_id = int (source_id_str .strip ())
457+ sources [source_id ] = {'name' : name .strip ()}
458+ except ValueError :
459+ continue
460+ return sources
461+
462+ def _zones_to_text (self , zones : dict [int , dict [str , str ]]) -> str :
463+ """Convert zones dict back to text format."""
464+ lines = []
465+ for zone_id in sorted (zones .keys ()):
466+ name = zones [zone_id ].get ('name' , f'Zone { zone_id } ' )
467+ lines .append (f'{ zone_id } : { name } ' )
468+ return '\n ' .join (lines )
469+
470+ def _sources_to_text (self , sources : dict [int , dict [str , str ]]) -> str :
471+ """Convert sources dict back to text format."""
472+ lines = []
473+ for source_id in sorted (sources .keys ()):
474+ name = sources [source_id ].get ('name' , f'Source { source_id } ' )
475+ lines .append (f'{ source_id } : { name } ' )
476+ return '\n ' .join (lines )
0 commit comments