@@ -175,6 +175,9 @@ defmodule UniversalProxy.Hardware do
175175 * `:slots` — explicit list of slot bus paths (overrides the
176176 target-based mapping; pass `nil` for dynamic mode)
177177 * `:saved_configs` — pre-built `%{serial_number => config}` map
178+ * `:line_settings` — pre-built `%{port_id => keyword()}` map of
179+ persisted UART line settings (see
180+ `UniversalProxy.UART.SettingsStore.all_opts/1`)
178181 * `:in_use_ports` — pre-built `MapSet` of currently-opened tty
179182 basenames
180183 * `:zwave_claim` — the Z-Wave proxy's port claim
@@ -197,6 +200,8 @@ defmodule UniversalProxy.Hardware do
197200 end )
198201 end )
199202
203+ line_settings = Keyword . get_lazy ( opts , :line_settings , & safe_line_settings / 0 )
204+
200205 usage = % {
201206 in_use_ports:
202207 Keyword . get_lazy ( opts , :in_use_ports , fn ->
@@ -211,11 +216,19 @@ defmodule UniversalProxy.Hardware do
211216 slots = Keyword . get ( opts , :slots , target_slots ( ) )
212217
213218 case slots do
214- nil -> dynamic_listing ( enumerated , bus_paths , saved , usage )
215- slots -> slot_listing ( slots , enumerated , bus_paths , saved , usage )
219+ nil -> dynamic_listing ( enumerated , bus_paths , saved , usage , line_settings )
220+ slots -> slot_listing ( slots , enumerated , bus_paths , saved , usage , line_settings )
216221 end
217222 end
218223
224+ # A wedged/absent SettingsStore must degrade to "no settings shown", not
225+ # take down the caller (public-API catch :exit idiom, CLAUDE.md).
226+ defp safe_line_settings do
227+ UART.SettingsStore . all_opts ( )
228+ catch
229+ :exit , _ -> % { }
230+ end
231+
219232 @ doc """
220233 Map of `{slot_sub, vendor_id, product_id} => tty_basename` for every
221234 currently-connected USB serial adapter. ESPHome consumers use this to
@@ -357,7 +370,7 @@ defmodule UniversalProxy.Hardware do
357370
358371 # -- Slot mode (per-target, every port shows) ------------------------
359372
360- defp slot_listing ( slots , enumerated , bus_paths , saved , usage ) do
373+ defp slot_listing ( slots , enumerated , bus_paths , saved , usage , line_settings ) do
361374 tty_by_bus_path = Map . new ( bus_paths , fn { tty , bus } -> { bus , tty } end )
362375 known_slots = MapSet . new ( slots )
363376
@@ -371,7 +384,7 @@ defmodule UniversalProxy.Hardware do
371384
372385 tty_name ->
373386 info = Map . get ( enumerated , tty_name , % { } )
374- build_port ( tty_name , info , slot_sub , saved , usage , idx )
387+ build_port ( tty_name , info , slot_sub , saved , usage , line_settings , idx )
375388 end
376389 end )
377390
@@ -384,23 +397,23 @@ defmodule UniversalProxy.Hardware do
384397 |> Enum . with_index ( length ( slots ) + 1 )
385398 |> Enum . map ( fn { { tty_name , bus_path } , idx } ->
386399 info = Map . get ( enumerated , tty_name , % { } )
387- build_port ( tty_name , info , bus_path , saved , usage , idx )
400+ build_port ( tty_name , info , bus_path , saved , usage , line_settings , idx )
388401 end )
389402
390403 declared ++ bonus
391404 end
392405
393406 # -- Dynamic mode (host / unknown target) ----------------------------
394407
395- defp dynamic_listing ( enumerated , bus_paths , saved , usage ) do
408+ defp dynamic_listing ( enumerated , bus_paths , saved , usage , line_settings ) do
396409 enumerated
397410 |> Enum . filter ( fn { name , _ } -> usb_serial? ( name ) end )
398411 |> Enum . map ( fn { name , info } -> { name , info , Map . get ( bus_paths , name ) } end )
399412 |> Enum . sort_by ( fn { name , _info , bus_path } -> { bus_path || "~" , name } end )
400413 |> Enum . with_index ( 1 )
401414 |> Enum . map ( fn { { name , info , bus_path } , idx } ->
402415 slot_sub = bus_path || name
403- build_port ( name , info , slot_sub , saved , usage , idx )
416+ build_port ( name , info , slot_sub , saved , usage , line_settings , idx )
404417 end )
405418 end
406419
@@ -458,7 +471,7 @@ defmodule UniversalProxy.Hardware do
458471
459472 # -- Port shape ------------------------------------------------------
460473
461- defp build_port ( tty_name , info , slot_sub , saved , usage , idx ) do
474+ defp build_port ( tty_name , info , slot_sub , saved , usage , line_settings , idx ) do
462475 vid = info [ :vendor_id ]
463476 pid = info [ :product_id ]
464477 serial = info [ :serial_number ]
@@ -470,6 +483,7 @@ defmodule UniversalProxy.Hardware do
470483 name = pick_name ( saved_cfg , device_info , info )
471484 vendor = pick_vendor ( device_info , info )
472485 chip = pick_chip ( device_info , info )
486+ line = Map . get ( line_settings , port_id ( slot_sub ) )
473487
474488 % {
475489 id: port_id ( slot_sub ) ,
@@ -494,12 +508,27 @@ defmodule UniversalProxy.Hardware do
494508 locked: classification . locked ,
495509 user: user ,
496510 user_href: nil ,
497- baud: nil ,
498511 throughput: % { in: 0 , out: 0 , unit: "B/s" } ,
499512 errors: 0 ,
500513 since: nil ,
501514 notes: classification . notes
502515 }
516+ |> Map . merge ( line_setting_fields ( line ) )
517+ end
518+
519+ # No persisted entry (never opened, or store unavailable) → `baud: nil`,
520+ # which `fact_rows/1` in the Overview LiveView treats as "hide the Serial
521+ # settings row". `parity` MUST be a string, never an atom — the LiveView
522+ # calls `String.first/1` on it.
523+ defp line_setting_fields ( nil ) , do: % { baud: nil , data_bits: nil , stop_bits: nil , parity: nil }
524+
525+ defp line_setting_fields ( opts ) do
526+ % {
527+ baud: opts [ :speed ] ,
528+ data_bits: opts [ :data_bits ] ,
529+ stop_bits: opts [ :stop_bits ] ,
530+ parity: to_string ( opts [ :parity ] || :none )
531+ }
503532 end
504533
505534 # Who holds this tty open, if anyone. The Z-Wave proxy owns its port
0 commit comments