Add UI config flow to Universal media player - #177656
Conversation
- Add config_flow.py with SchemaConfigFlowHandler providing a two-step wizard: basic setup (name + child players) then command routing (turn_on, turn_off, volume_up, volume_down, volume_mute, select_source via ActionSelector) - Add options flow so users can reconfigure child players and command overrides after initial setup - Add async_step_import to migrate YAML configs that carry a unique_id into UI-managed config entries automatically on startup - Add async_setup_entry / async_unload_entry to __init__.py forwarding to the media_player platform - Add async_setup_entry to media_player.py; existing UniversalMediaPlayer entity class is unchanged - Update manifest.json with config_flow: true - Add config/options flow strings to strings.json and regenerate translations/en.json
Also fixes the config and options flows, marks the integration as a helper so the config flow may include a name field, and preserves the original unique_id when migrating a YAML entry with a unique_id to a config entry so the entity registry entry is not orphaned.
There was a problem hiding this comment.
Hi @gmihovics
It seems you haven't yet signed a CLA. Please do so here.
Once you do that we will be able to review and accept this pull request.
Thanks!
There was a problem hiding this comment.
🟡 Not ready to approve
YAML migration loses supported behavior, action sequences are truncated, and option changes are not applied until reload.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.
Pull request overview
Adds UI-based configuration, options editing, and YAML migration for Universal media players.
Changes:
- Adds config-entry setup and unloading.
- Adds user, options, and YAML-import flows.
- Adds tests, UI strings, and generated metadata.
File summaries
| File | Description |
|---|---|
homeassistant/components/universal/__init__.py |
Adds config-entry lifecycle handling. |
homeassistant/components/universal/config_flow.py |
Implements configuration, options, and import flows. |
homeassistant/components/universal/manifest.json |
Enables config flow and helper classification. |
homeassistant/components/universal/media_player.py |
Adds config-entry entity setup and YAML migration. |
homeassistant/components/universal/strings.json |
Adds flow labels and descriptions. |
homeassistant/generated/config_flows.py |
Registers the helper config flow. |
homeassistant/generated/integrations.json |
Updates generated integration metadata. |
tests/components/universal/test_config_flow.py |
Tests user, options, and import flows. |
tests/components/universal/test_media_player.py |
Tests config-entry entity behavior and commands. |
Review details
Comments suppressed due to low confidence (2)
homeassistant/components/universal/config_flow.py:125
- Preserve all supported YAML settings when creating the imported entry. This options mapping drops
device_class,active_child_template, andstate_template, even thoughasync_setup_entryreads those values, so imported players lose their configured class and template-driven behavior.
options: dict[str, Any] = {
CONF_NAME: import_data[CONF_NAME],
CONF_CHILDREN: import_data.get(CONF_CHILDREN, []),
# Preserve JSON-serialisable advanced options so they survive the
# round-trip through config entry storage unchanged.
CONF_BROWSE_MEDIA_ENTITY: import_data.get(CONF_BROWSE_MEDIA_ENTITY),
homeassistant/components/universal/config_flow.py:137
- Preserve YAML command overrides outside
EXPOSED_COMMANDS. The platform schema accepts mappings such asvolume_set,media_play,play_media, andshuffle_set, and the entity implements those overrides, but this loop discards them during migration and changes the entity's supported features and routing.
for cmd in EXPOSED_COMMANDS:
options[cmd] = [yaml_commands[cmd]] if cmd in yaml_commands else []
- Files reviewed: 7/9 changed files
- Comments generated: 7
- Review effort level: Medium
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
| if config.get(CONF_UNIQUE_ID): | ||
| # Migrate YAML entries that carry a unique_id to a config entry so they | ||
| # become manageable via the UI. The entity is created by async_setup_entry | ||
| # once the import flow completes; return early to avoid a duplicate. | ||
| hass.async_create_task( |
| actions = options.get(cmd, []) | ||
| if isinstance(actions, list) and actions: | ||
| commands[cmd] = actions[0] |
| "domain": "universal", | ||
| "name": "Universal media player", | ||
| "codeowners": [], | ||
| "config_flow": true, |
| config_flow = CONFIG_FLOW | ||
| options_flow = OPTIONS_FLOW |
| # Commands in YAML are single service-call dicts; wrap in a list to | ||
| # match the ActionSelector storage format used by the UI flow. | ||
| yaml_commands: dict[str, Any] = import_data.get(CONF_COMMANDS, {}) |
| "turn_off": "Action to run when the player is turned off.", | ||
| "turn_on": "Action to run when the player is turned on (e.g. power on an AV receiver or run a script).", | ||
| "volume_down": "Action to run when volume down is pressed.", | ||
| "volume_mute": "Action to run when muting or unmuting. Receives variable `media_player_volume_muted` (boolean).", |
| "Universal media player '%s' is configured via YAML, which is " | ||
| "deprecated. Remove it from your configuration.yaml and recreate " | ||
| "it through the UI (Settings → Devices & services → Add integration " | ||
| "→ Universal media player)" |
Adds two more config/options flow steps: attribute overrides (state, is_volume_muted, volume_level, source, source_list, sound_mode, sound_mode_list) and advanced options (device_class, browse_media_entity, active_child_template, state_template). Also expands command routing from 6 to 13 commands, matching the most commonly used media_player commands documented for YAML. Also fixes async_step_import, which silently dropped device_class, active_child_template, and state_template when migrating a YAML config with a unique_id.
There was a problem hiding this comment.
🟡 Not ready to approve
Migration can lose commands, while options reload and action-sequence handling are incomplete.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.
Review details
Comments suppressed due to low confidence (7)
homeassistant/components/universal/media_player.py:175
- Trigger the import flow for YAML entries without a
unique_idas well. This guard bypassesasync_step_importand itsyaml_fallback during real YAML setup, so those entries remain YAML-only despite the stated migration behavior; the direct flow test does not exercise this setup path.
if config.get(CONF_UNIQUE_ID):
# Migrate YAML entries that carry a unique_id to a config entry so they
# become manageable via the UI. The entity is created by async_setup_entry
# once the import flow completes; return early to avoid a duplicate.
hass.async_create_task(
homeassistant/components/universal/media_player.py:203
- Execute the complete action sequence returned by
ActionSelector. It explicitly represents a script sequence, so selecting onlyactions[0]silently drops later actions and fails when the first item is a non-service action such as a delay or condition; use aScript-based runner for the stored sequence instead.
if isinstance(actions, list) and actions:
commands[cmd] = actions[0]
homeassistant/components/universal/strings.json:70
- Document the actual mute variable name.
_async_call_servicepassesATTR_MEDIA_VOLUME_MUTED, whose value isis_volume_muted, so the documentedmedia_player_volume_mutedvariable is undefined in the configured action.
"volume_mute": "Action to run when muting or unmuting. Receives variable `media_player_volume_muted` (boolean).",
homeassistant/components/universal/config_flow.py:192
- Tell users that the entry has already been imported instead of asking them to recreate it. Following the current warning creates a second UI entry even though
async_create_entrybelow has already persisted the migrated one.
"Universal media player '%s' is configured via YAML, which is "
"deprecated. Remove it from your configuration.yaml and recreate "
"it through the UI (Settings → Devices & services → Add integration "
"→ Universal media player)"
homeassistant/components/universal/config_flow.py:143
- Enable automatic reloads when the options flow finishes.
SchemaConfigFlowHandlerdefaultsoptions_flow_reloadstoFalse, so these updated children, commands, and attributes are only written to storage while the loaded entity keeps its old configuration until a manual reload or restart.
options_flow = OPTIONS_FLOW
homeassistant/components/universal/strings.json:147
- Add the missing options-flow label and description for the
media_playcommand. The options form exposes the same command but currently has no translation references for it.
"media_pause": "[%key:component::universal::config::step::commands::data::media_pause%]",
homeassistant/components/universal/strings.json:62
- Add the missing config-flow label and description for the
media_playcommand.EXPOSED_COMMANDSadds this field to the form, but these translation mappings omit it.
"media_pause": "Pause",
- Files reviewed: 7/9 changed files
- Comments generated: 1
- Review effort level: Medium
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
| for cmd in EXPOSED_COMMANDS: | ||
| options[cmd] = [yaml_commands[cmd]] if cmd in yaml_commands else [] |
DEVICE_CLASSES_SCHEMA coerces device_class to a MediaPlayerDeviceClass StrEnum, and CMD_SCHEMA compiles templated command data/target values into Template objects; storing either directly in config entry options crashed the config entries store since neither is JSON-serialisable. Both are now flattened to plain strings on import, and async_setup_entry recompiles command data/target templates via SERVICE_SCHEMA so they still render at call time, since async_call_from_config is invoked with validate_config=False.
There was a problem hiding this comment.
🟡 Not ready to approve
Action sequences are mishandled, option edits are not reloaded, and YAML migration can skip or lose configuration.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.
Review details
Comments suppressed due to low confidence (5)
homeassistant/components/universal/config_flow.py:209
- Preserve all supported YAML command overrides during import. Restricting this loop to
EXPOSED_COMMANDSdrops existing overrides such asplay_media,media_play_pause,toggle,clear_playlist,repeat_set, andshuffle_set; the migrated entity then silently changes behavior becauseasync_setup_entryalso reads only this tuple.
for cmd in EXPOSED_COMMANDS:
options[cmd] = (
[_flatten_templates(yaml_commands[cmd])] if cmd in yaml_commands else []
)
homeassistant/components/universal/config_flow.py:216
- Tell users that the YAML configuration was imported instead of asking them to recreate it. This warning is emitted immediately before creating the config entry, so recreating it manually can produce a duplicate; instruct users only to remove the migrated YAML.
"Universal media player '%s' is configured via YAML, which is "
"deprecated. Remove it from your configuration.yaml and recreate "
"it through the UI (Settings → Devices & services → Add integration "
"→ Universal media player)"
homeassistant/components/universal/media_player.py:171
- Run the import for YAML entries that lack
unique_idtoo. This guard prevents theyaml_<name>fallback inasync_step_importfrom being reached during real platform setup, so those players are not migrated as described; the direct flow test does not exercise this path.
if config.get(CONF_UNIQUE_ID):
homeassistant/components/universal/config_flow.py:161
- Reload the config entry after an options flow is saved.
SchemaConfigFlowHandlerdefaultsoptions_flow_reloadstoFalse, so the running player keeps its old children, commands, attributes, and templates until a restart or manual reload.
This issue also appears in the following locations of the same file:
- line 206
- line 213
config_flow = CONFIG_FLOW
options_flow = OPTIONS_FLOW
homeassistant/components/universal/strings.json:70
- Document the actual mute-action variable name.
async_mute_volumepassesATTR_MEDIA_VOLUME_MUTED, whose value isis_volume_muted, so templates following this description receive an undefinedmedia_player_volume_mutedvariable.
"volume_mute": "Action to run when muting or unmuting. Receives variable `media_player_volume_muted` (boolean).",
- Files reviewed: 7/9 changed files
- Comments generated: 1
- Review effort level: Medium
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
| for cmd in EXPOSED_COMMANDS: | ||
| actions = options.get(cmd, []) | ||
| if isinstance(actions, list) and actions: | ||
| commands[cmd] = cv.SERVICE_SCHEMA(actions[0]) |
media_play was left out of strings.json's commands step (both data and data_description, in config and options), so it rendered as the raw key name instead of "Play" in the command routing form.
There was a problem hiding this comment.
🟡 Not ready to approve
Valid UI actions, option updates, and some migrated YAML configurations currently produce incorrect runtime behavior.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.
Review details
Comments suppressed due to low confidence (6)
homeassistant/components/universal/config_flow.py:161
- Enable automatic reloads for this options flow.
SchemaConfigFlowHandler.options_flow_reloadsdefaults toFalse, so saving new children, commands, or attributes updates storage but leaves the loaded entity using its old configuration until a manual reload or restart; set this flag toTrueas similar helper flows do.
config_flow = CONFIG_FLOW
options_flow = OPTIONS_FLOW
homeassistant/components/universal/media_player.py:208
- Execute the full action sequence accepted by
ActionSelector. The selector accepts arbitrary script actions and multiple actions, but this keeps only the first item and validates it as a service call, so later actions are silently discarded and valid actions such asdelay,choose, or device actions make config-entry setup fail; run the stored sequence through the script engine or restrict and validate the flow input as one service call.
for cmd in EXPOSED_COMMANDS:
actions = options.get(cmd, [])
if isinstance(actions, list) and actions:
commands[cmd] = cv.SERVICE_SCHEMA(actions[0])
homeassistant/components/universal/config_flow.py:209
- Preserve YAML command overrides that are not exposed in the UI. Import currently copies only
EXPOSED_COMMANDS, so automatically migrated players lose supported overrides such asplay_media,clear_playlist,shuffle_set,repeat_set,media_play_pause, andtoggle; retain hidden commands in config-entry options and merge them during setup, as is already done for hidden attributes.
for cmd in EXPOSED_COMMANDS:
options[cmd] = (
[_flatten_templates(yaml_commands[cmd])] if cmd in yaml_commands else []
)
homeassistant/components/universal/media_player.py:175
- Start the import flow for YAML players without a
unique_idtoo. This guard makes theyaml_fallback inasync_step_importunreachable during real YAML setup, so those players remain YAML entities despite the stated migration behavior; the current test only invokes the import flow directly and does not exercise this platform path.
if config.get(CONF_UNIQUE_ID):
# Migrate YAML entries that carry a unique_id to a config entry so they
# become manageable via the UI. The entity is created by async_setup_entry
# once the import flow completes; return early to avoid a duplicate.
hass.async_create_task(
homeassistant/components/universal/config_flow.py:216
- Tell users that the player was already imported instead of asking them to recreate it. This warning is emitted immediately before creating the config entry, so following its current instruction creates a duplicate UI player after the YAML entry is removed.
"Universal media player '%s' is configured via YAML, which is "
"deprecated. Remove it from your configuration.yaml and recreate "
"it through the UI (Settings → Devices & services → Add integration "
"→ Universal media player)"
homeassistant/components/universal/strings.json:72
- Document the actual mute template variable.
async_mute_volumepassesATTR_MEDIA_VOLUME_MUTED, whose value isis_volume_muted, somedia_player_volume_mutedis undefined when this action renders.
"volume_mute": "Action to run when muting or unmuting. Receives variable `media_player_volume_muted` (boolean).",
- Files reviewed: 7/9 changed files
- Comments generated: 0 new
- Review effort level: Medium
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
There was a problem hiding this comment.
🟡 Not ready to approve
YAML migration can drop command overrides, options do not reload, and action sequences are handled incorrectly.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.
Review details
Comments suppressed due to low confidence (5)
homeassistant/components/universal/media_player.py:208
- Execute the complete action sequence selected by the user.
ActionSelectoraccepts a script sequence, but indexing[0]silently discards subsequent actions, and validating that item withSERVICE_SCHEMAalso makes valid non-service actions such as delays or conditions fail config-entry setup.
if isinstance(actions, list) and actions:
commands[cmd] = cv.SERVICE_SCHEMA(actions[0])
homeassistant/components/universal/config_flow.py:209
- Preserve every supported YAML command when migrating the entity. Iterating only
EXPOSED_COMMANDSdrops existing overrides such asplay_media,media_play_pause,clear_playlist,shuffle_set,repeat_set, andtoggle; because the YAML platform returns early after import, those commands stop working immediately even while the YAML remains present.
for cmd in EXPOSED_COMMANDS:
options[cmd] = (
[_flatten_templates(yaml_commands[cmd])] if cmd in yaml_commands else []
)
homeassistant/components/universal/media_player.py:171
- Start the import flow for YAML entries without unique IDs as well. The
yaml_fallback inasync_step_importis otherwise unreachable during normal YAML setup, so such entities remain YAML-only despite the stated migration behavior and the new fallback test.
if config.get(CONF_UNIQUE_ID):
homeassistant/components/universal/config_flow.py:216
- Tell the user to remove the YAML configuration without recreating the entry. This warning is emitted after
async_create_entryhas already imported the player, so instructing users to recreate it is inaccurate and can discard the entity-registry-preserving migration this flow just performed.
"Universal media player '%s' is configured via YAML, which is "
"deprecated. Remove it from your configuration.yaml and recreate "
"it through the UI (Settings → Devices & services → Add integration "
"→ Universal media player)"
homeassistant/components/universal/config_flow.py:161
- Reload the config entry after the options flow completes.
SchemaConfigFlowHandlerdefaultsoptions_flow_reloadstoFalse, so these edits are persisted but the existing media-player entity continues using its old children, commands, and attributes until Home Assistant restarts.
options_flow = OPTIONS_FLOW
- Files reviewed: 7/9 changed files
- Comments generated: 0 new
- Review effort level: Medium
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
Without options_flow_reloads, editing children/commands/attributes through Configure persisted to storage but the running entity kept its old in-memory configuration until Home Assistant restarted. Every comparable helper (threshold, min_max, derivative, generic_thermostat) already sets this.
YAML's commands: accepts arbitrary slug keys (play_media, toggle, repeat_set, shuffle_set, clear_playlist, etc.), not just the 13 exposed in the UI. async_step_import previously only carried over EXPOSED_COMMANDS, silently turning any other override into delegation to the active child. They're now preserved under a CONF_COMMANDS key and merged back in by async_setup_entry.
The YAML entry has already been migrated to a config entry by this point; telling users to "recreate" it through the UI could lead them to create a second, duplicate Universal player instead of managing the migrated one.
The entity passes ATTR_MEDIA_VOLUME_MUTED (whose value is is_volume_muted), not media_player_volume_muted, as the mute state variable for the overriding action.
There was a problem hiding this comment.
🟡 Not ready to approve
Action sequences, self-referential selections, YAML migration, and user-facing guidance contain unresolved functional issues.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.
Review details
Comments suppressed due to low confidence (6)
homeassistant/components/universal/media_player.py:208
- Execute the complete action sequence rather than validating only
actions[0].ActionSelectoraccepts script sequences, so this drops later actions and makes valid actions such asdelayorchoosefail setup underSERVICE_SCHEMA; run the sequence as aScriptor constrain the input to one service call.
commands[cmd] = cv.SERVICE_SCHEMA(actions[0])
homeassistant/components/universal/media_player.py:171
- Import YAML entries without a
unique_idthrough the flow as well. This guard leaves those entries in legacy YAML setup, so the documentedyaml_fallback inasync_step_importis never reached during normal startup.
if config.get(CONF_UNIQUE_ID):
homeassistant/components/universal/config_flow.py:76
- Exclude the entry's own media-player entity from the options child selector. Selecting it makes the universal player observe and delegate services to itself, which can recurse; use a dynamic schema with
entity_selector_without_own_entities, as inhomeassistant/components/group/config_flow.py:52-65.
_OPTIONS_BASIC_SCHEMA = vol.Schema(
{
vol.Optional(CONF_CHILDREN, default=[]): _CHILDREN_SELECTOR,
}
homeassistant/components/universal/config_flow.py:228
- Tell users that the entry was already imported instead of asking them to recreate it. This flow immediately creates the config entry, so following the current warning can create a duplicate universal player.
"Universal media player '%s' has been migrated from YAML to a "
"config entry. Remove it from your configuration.yaml; you can "
"now manage it from Settings → Devices & services"
),
homeassistant/components/universal/strings.json:72
- Document the actual mute template variable. The command receives
is_volume_mutedfromATTR_MEDIA_VOLUME_MUTED, so the advertised name renders undefined.
"volume_mute": "Action to run when muting or unmuting. Receives variable `is_volume_muted` (boolean).",
homeassistant/components/universal/strings.json:15
- Describe the default active-child selection accurately. The implementation chooses the child with the highest-priority playback state, not simply the first child that is on.
"active_child_template": "A template that returns the entity ID of the child to treat as active, or `None` to use the default behavior (the first child that is on).",
- Files reviewed: 7/9 changed files
- Comments generated: 1
- Review effort level: Medium
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
| vol.Optional(CONF_BROWSE_MEDIA_ENTITY): selector.EntitySelector( | ||
| selector.EntitySelectorConfig(domain="media_player") | ||
| ), |
There was a problem hiding this comment.
🟡 Not ready to approve
YAML migration remains incomplete, and valid multi-action selections are truncated or can prevent setup.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.
Review details
Comments suppressed due to low confidence (3)
homeassistant/components/universal/media_player.py:171
- Import every YAML definition, not only those with a
unique_id. This guard bypassesasync_step_importfor configurations without one, so itsyaml_fallback is never used and those players remain YAML-only despite the migration promised by this PR.
if config.get(CONF_UNIQUE_ID):
homeassistant/components/universal/media_player.py:208
- Execute the complete action sequence instead of coercing only its first item to a service call.
ActionSelectoraccepts arbitrary multi-step script syntax, so this drops later actions and makes config-entry setup fail when the first valid action is a delay, condition, or device action; use the script execution pattern used by template entities.
commands[cmd] = cv.SERVICE_SCHEMA(actions[0])
homeassistant/components/universal/strings.json:15
- Describe the fallback as selecting the child with the highest-priority active state. The implementation ranks states through
playingand only preserves child order for ties, so it does not simply pick the first child that is on.
"active_child_template": "A template that returns the entity ID of the child to treat as active, or `None` to use the default behavior (the first child that is on).",
- Files reviewed: 7/9 changed files
- Comments generated: 0 new
- Review effort level: Medium
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
No other comparable helper integration (threshold, min_max, derivative, filter, generic_thermostat, statistics) migrates YAML to a config entry; YAML and the UI live in permanent parallel for all of them, with the config flow only used for new UI-created instances. Since the config flow doesn't yet cover every option YAML supports, following that same pattern avoids forcing a YAML user into a config entry that can't fully replicate their existing setup. Removes async_step_import and the now-unreachable SOURCE_IMPORT trigger in async_setup_platform, along with the now-dead config-entry-storage handling that only existed to support import (the CONF_COMMANDS merge-in for commands outside EXPOSED_COMMANDS, and the list-shaped CONF_ATTRS/actions.dict branches). YAML keeps working exactly as before, unchanged.
There was a problem hiding this comment.
🟡 Not ready to approve
YAML import is missing, action sequences are mishandled, and options editing drops unexposed attributes.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.
Review details
Comments suppressed due to low confidence (5)
homeassistant/components/universal/media_player.py:195
- Execute or reject the complete action sequence instead of silently taking its first item.
ActionSelectoraccepts arbitrary script sequences, so valid multi-action inputs lose every action after the first, and valid non-service first actions such asdelayorchoosemakeSERVICE_SCHEMAfail during entry setup.
if isinstance(actions, list) and actions:
commands[cmd] = cv.SERVICE_SCHEMA(actions[0])
homeassistant/components/universal/config_flow.py:144
- Add the missing YAML import path before advertising migration.
SchemaConfigFlowHandleronly generates the four steps inCONFIG_FLOW, so this class has noasync_step_import, whileasync_setup_platformstill creates the YAML entity directly; existing YAML is therefore never migrated into a config entry.
class UniversalFlowHandler(SchemaConfigFlowHandler, domain=DOMAIN):
"""Handle a Universal media player config and options flow."""
config_flow = CONFIG_FLOW
options_flow = OPTIONS_FLOW
homeassistant/components/universal/media_player.py:211
- Preserve the config entry's imported unique ID when creating the entity. Using
entry_idunconditionally gives a migrated YAML entity a new registry identity, so its existing entity-registry history is not retained as described.
CONF_UNIQUE_ID: config_entry.entry_id,
homeassistant/components/universal/config_flow.py:104
- Merge unexposed stored attributes when updating the UI fields. Replacing
CONF_ATTRSwith onlyEXPOSED_ATTRIBUTESdeletes every YAML-only override after an options-flow save, contrary to the stated preservation behavior.
return {
CONF_ATTRS: {
attr: user_input[attr]
for attr in EXPOSED_ATTRIBUTES
if user_input.get(attr)
homeassistant/components/universal/strings.json:12
- Describe the default active-child selection accurately. The implementation scans all children and chooses the highest-priority playback state, not simply the first child that is on.
"active_child_template": "A template that returns the entity ID of the child to treat as active, or `None` to use the default behavior (the first child that is on).",
- Files reviewed: 7/9 changed files
- Comments generated: 0 new
- Review effort level: Medium
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
Proposed change
Adds a UI config flow to the Universal media player integration, which was previously YAML-only. YAML configuration continues to work unchanged; the config flow is an additional way to create new Universal media players through the UI, consistent with how every other comparable helper integration (
filter,generic_thermostat,threshold,min_max,derivative,statistics) keeps YAML and the config flow as independent, parallel paths rather than migrating one into the other.async_setup_entry/async_unload_entryand a 4-step config flow: name + children, command routing (13 commands viaActionSelector:turn_on,turn_off,volume_up,volume_down,volume_mute,volume_set,select_source,select_sound_mode,media_play,media_pause,media_stop,media_next_track,media_previous_track), attribute overrides (state,is_volume_muted,volume_level,source,source_list,sound_mode,sound_mode_list), and advanced options (device_class,browse_media_entity,active_child_template,state_template).options_flow_reloads = Trueso edits take effect immediately instead of requiring a restart.filterandgeneric_thermostatsplit UI vs. YAML configuration.Type of change
Additional information
Checklist
ruff format homeassistant tests)If user exposed functionality or configuration variables are added/changed:
If the code communicates with devices, web services, or third-party tools:
Updated and included derived files by running:
python3 -m script.hassfest.requirements_all.txt.Updated by running
python3 -m script.gen_requirements_all.To help with the load of incoming pull requests: