-
-
Notifications
You must be signed in to change notification settings - Fork 30.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add config_flow to bluesound integration #115207
Add config_flow to bluesound integration #115207
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @LouisChrist
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!
Hey there @thrawnarn, mind taking a look at this pull request as it has been labeled with an integration ( Code owner commandsCode owners of
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Before this integration can be migrated to a config flow, it should adhere to ADR-0010, which states that all code used to connect to a device or service should be encapsulated in a python library published on pypi.
Please take a look at the requested changes, and use the Ready for review button when you are done, thanks 👍 |
My Bad. My intent was to do it the other way around as this seemed to be the smaller task. I'm working on a library in parallel: pyblu. Should I do all that in one PR or should I split it into a separate PR? |
db6a4bc
to
19d87db
Compare
I created and integrated a separate library for interacting with the devices: pyblu. The parts regarding join/unjoin of devices are not tested with real devices as i have only one. They are tested to the degree that they do not return any 'Bad Request'. And there are unit tests in the library, which are based on the documentation. I tried to change as little as possible in the integration itself. |
Can we first do a prelimary PR to make the integration use the library? |
I created a separate PR: Integrate pypi library: pyblu |
19d87db
to
a64e8ee
Compare
Wow great work in getting Bluesound up to standards with a real config-flow. I didn't look before I created my own config-flow (with zeroconf discovery). Perhaps you find something useful from my code (or once we get this merged I can create a new PR with the zeroconf code). |
Thank you. I can integrate your zeroconf code into this PR, if you are ok with that. It seems to be a rather small addition. Or you can do it as a separate PR(doing zeroconf a separate PR was my initial plan). That is your decision, since it's your code i would be copying. |
Feel free to use "my" code in any way you want. I didn't think there were anyone actively developing for Bluesound and was quite annoyed that the integration didn't support config registry. |
What are we going to do with the entities in hass.data? They should be removed but if we keep the logic the same we can do that in a followup, wdyt? |
I would do the |
I know, but i am wondering, because you moved the service things to the |
I moved it because of a circular dependency. I could move |
I think you can do that, but instead of moving the config entry type you can do if TYPE_CHECKING:
from x import x It might also need I think that might work |
_LOGGER.info("Added device with name: %s", player.name) | ||
hass.data[DATA_BLUESOUND].append(bluesound_player) | ||
async_add_entities([bluesound_player]) | ||
_LOGGER.info("Added device with name: %s", bluesound_player.name) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't log on info
if result["type"] == FlowResultType.ABORT: | ||
ir.async_create_issue( | ||
hass, | ||
DOMAIN, | ||
f"deprecated_yaml_import_issue_{result['reason']}", | ||
breaks_in_ha_version="2025.2.0", | ||
is_fixable=False, | ||
issue_domain=DOMAIN, | ||
severity=ir.IssueSeverity.WARNING, | ||
translation_key=f"deprecated_yaml_import_issue_{result['reason']}", | ||
translation_placeholders={ | ||
"domain": DOMAIN, | ||
"integration_title": INTEGRATION_TITLE, | ||
}, | ||
) | ||
return |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we can't import because the connection failed (cannot connect) we should raise a different issue. Please check incomfort for an example
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh right, we do that here, but if we abort because the config entry already exists, we should raise the generic one
host = config_entry.data.get(CONF_HOST) | ||
port = config_entry.data.get(CONF_PORT) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
host = config_entry.data.get(CONF_HOST) | |
port = config_entry.data.get(CONF_PORT) | |
host = config_entry.data[CONF_HOST] | |
port = config_entry.data[CONF_PORT] |
assert isinstance(host, str) | ||
assert isinstance(port, int) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Double check if we need this after the previous comment
"description": "[%key:common::config_flow::description::confirm_setup%]" | ||
} | ||
}, | ||
"abort": { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think zeroconf also requires the flow_in_progress
translation key
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I cannot find flow_in_progress
. Do you mean already_in_progress
? That one seems to be thrown in async_set_unique_id
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Yes! I am on mobile so couldn't check the code
assert result["type"] is FlowResultType.CREATE_ENTRY | ||
assert result["title"] == "player-name" | ||
assert result["data"] == {CONF_HOST: "1.1.1.1", CONF_PORT: 11000} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's also assert unique id?
}, | ||
) | ||
|
||
assert result["type"] is FlowResultType.FORM |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should test if the flow is able to recover an error, so rewatch and make sure we finish the flow
hass: HomeAssistant, mock_setup_entry: AsyncMock, mock_player_sync_status: AsyncMock | ||
) -> None: | ||
"""Test we get the form.""" | ||
hass.data[DOMAIN] = [] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't touch internals
mock_entry = MockConfigEntry( | ||
domain=DOMAIN, | ||
data={ | ||
CONF_HOST: "1.1.1.1", | ||
CONF_PORT: 11000, | ||
}, | ||
unique_id="00:11:22:33:44:55-11000", | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can make this a fixture
assert result["type"] is FlowResultType.FORM | ||
assert result["step_id"] == "confirm" | ||
|
||
mock_setup_entry.assert_not_called() | ||
mock_player_sync_status.assert_called_once() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's also make this flow finish in a create entry
I think this is the last round. The beta cut for 2024.8 is next Wednesday, so I'm confident we make it |
I also made a small change: Zeroconf now uses the provided port(it was always the default port before). |
I fixed some tiny things to unify the fixtures and a small typo, didn't feel like burdening you with another pass. Thanks for helping out. Can you also send you discord username? (Or send me a message) |
player | ||
for player in hass.data[DATA_BLUESOUND] | ||
if player.entity_id in entity_ids | ||
player for player in hass.data[DOMAIN] if player.entity_id in entity_ids |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Side note: In the future this should be refactored to not store the entities in hass.data but use our entity service helper to register entity services, and also use our standard ways to group media player entities (which doesn't require custom services).
https://developers.home-assistant.io/docs/core/entity/media-player#grouping-player-entities-together
To keep track of added entities we can eg store the player id. Look at the heos integration for an example how to map players to media player entities and group media players.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I already have a branch for implementing the standard way to group media player entities. My solution was:
- get
entity_entry
fromentity_registry
- get
config_entry
fromhass.config_entries.async_get_entry(entity_entry.config_entry_id)
- the config_entry has everything needed for grouping
But there is one big problem: I have only one Player. I cannot test it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are there any examples for entity services
? I couldn't find any.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here's an example:
core/homeassistant/components/denonavr/media_player.py
Lines 142 to 147 in 8bc9fd2
platform = entity_platform.async_get_current_platform() | |
platform.async_register_entity_service( | |
SERVICE_GET_COMMAND, | |
{vol.Required(ATTR_COMMAND): cv.string}, | |
f"async_{SERVICE_GET_COMMAND}", | |
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But there is one big problem: I have only one Player. I cannot test it.
Written tests are enough, if you can't test it manually.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have a few players and I'm happy to help with testing over the next week or two. Can you point me to the release or branch that needs to be tested, and give a brief description of the testing that you'd like?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That would be great. I don't have working code yet. And I have 2 things i want to do first before refactoring the media player grouping. I can tag you on the media player grouping PR once I have created it, but it will most likely be more that two weeks from now.
Proposed change
The documentation for using the
configuration.yaml
is removed and replaced by documentation for using the config_flow(see matching PR for docs)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
..coveragerc
.To help with the load of incoming pull requests: