-
-
Notifications
You must be signed in to change notification settings - Fork 34.1k
Refactor Songpal integration to use a coordinator #147226
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
base: dev
Are you sure you want to change the base?
Refactor Songpal integration to use a coordinator #147226
Conversation
Also prepares re-usable functionality which will be used to create additional entities to expose various Songpal settings. Since this is a significant refactoring, I have also added myself to the codeowners list to indicate my willingness to provide ongoing support.
Hey there @rytilahti, @shenxn, mind taking a look at this pull request as it has been labeled with an integration ( Code owner commandsCode owners of
|
First of all, thanks for working on this! I will see if I can do some quick reviewing on the train, but I wanted to briefly write that we should not create entities completely dynamically, but create descriptions for the features we want to support and initialize only them. You can take a look at how the tplink integration does it for an example. |
Ah ok. I was following the MQTT integration as a reference. Since I only have a single Songpal device and therefore I'm not sure what settings might/might not be available on other devices, I figured it was best to just support whatever the device reports. What if I instead add a list of all the settings I want support for (including manually written titles, unique IDs, descriptions, etc...) and then make the integration only create entities for ones which are supported by the device? Then other people would easily be able to add support for other settings on other devices in future. Would that work? |
That was turned down for tplink, I think mqtt being a very special case due to the variety of devices from different vendors. Personally, I would be open for a dynamic approach, but doing it in a controlled manner will likely provide a better user experience :-) How we decided to go was to define descriptions for known features and add debug logging to make it visible when some unsupported setting/sensor is encountered. This will allow us to extend the support gradually when we encounter new features that make sense to add, as some features in your screenshot (like timezone information) is probably not something we should do. The upstream project contains some devinfo dumps, which can be useful to see what information and settings some other devices support. Alas, I don't personally have the device I used for developing the library, so I cannot help with testing on a real device. |
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 split adding the base entity from adding a coordinator first as this PR is currently still doing too much
Please take a look at the requested changes, and use the Ready for review button when you are done, thanks 👍 |
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.
Some comments inline based on a brief review, the tests should be adjusted/added accordingly.
try: | ||
data = self.data | ||
|
||
if "sysinfo" not in data: |
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.
This sounds wrong for a full refresh, no?
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.
Previously this data was only requested on system startup, so I've carried that over. The idea is that polling_refresh should fetch data that doesn't raise change notifications on the websocket, whereas full_refresh should be called to make sure our state is up-to-date after an outage or disconnect.
If we think there's a significant likelihood that we need to monitor for changes to values in sysinfo or interface_info, we should probably move them to polling_refresh. Or I'd be happy to consider alternative names for polling_refresh vs full_refresh.
if not volumes: | ||
_LOGGER.error("Got no volume controls, bailing out") | ||
self.available = False | ||
return data |
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.
Unsure, but should we really still bail out here if only a single call fails? Or should we have a similar behaviour for the other calls, and maybe even clear the internal state?
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'm not sure if this condition is reachable tbh. I can't think of a reason why any Songpal would return zero volumes without raising an error. I just left this as-is.
If you're comfortable with just updating the state to have zero volumes then I'm comfortable with that too.
If we want to tolerate the failure of a single pull, I could also wrap each pull in a try/except? That way we can still partially update state even if something goes wrong? I could set a dirty flag so that the next polling interval triggers a full refresh instead of just a polling refresh.
This will be split into a separate PR
_LOGGER = logging.getLogger(__name__) | ||
|
||
|
||
class SongpalBaseEntity(CoordinatorEntity): |
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 let's implement this in a separate PR, a lot of things we can improve here
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 moved SongpalSettingEntity to #147237, but media_player is depending on SongpalBaseEntity in this commit. I'm not eager to change that.
To be honest it's already a pretty big headache working across 6 branches for what is essentially a single atomic change. (None of these PRs are really worth anything without all of these PRs, and every one of these PRs needs to be tested with the later PRs to make sure everything will work correctly later on.)
I'd rather not further increase the testing surface by introducing another temporary intermediary stage that the code passes through.
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.
To be honest it's already a pretty big headache working across 6 branches for what is essentially a single atomic change.
We also don't recommend working on 6 branches at a time, looking at the code there are many improvements to be made and we should go step by step
None of these PRs are really worth anything without all of these PRs
But they do change the codebase in a meaningful way and that is worth zooming into. The bigger the PR the slower it goes. Trust me, I review quite a lot :)
and every one of these PRs needs to be tested with the later PRs to make sure everything will work correctly later on
Write unit tests, I can help you with that once we're at that point
But yes, I still think this PR is doing too much right now since adding a coordinator does not mean adding a base entity
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 iterated on the coordinator quite a bit based on the things I discovered while implementing the various new entities, and while testing the whole thing end-to-end. If it wasn't for developing those entities, the controller wouldn't look like it does now. The requirements of the entities were an important part of the 'conversation' I was having with the type checker and the linter while writing the coordinator.
I don't think I could've made this PR without also making the changes that are now in the later PRs.
Anyway, if moving the creation of the base class into the second PR is what you require then I'll do that in a couple days.
I didn't mean to commit these in the first place oops
That would depend on if features can change on runtime (firmware updates, preset mode buttons), but even that isn't a complete answer, without more context. |
Adds typing and removes unnecessary dataclass
9dd41a5
to
46cc5bd
Compare
02e3296
to
6aaaaa8
Compare
…to songpal-coordinator-refactor
For now I think I've implemented almost all the feedback that's relevant to this PR, and have replied to review comments for anything I haven't implemented. And now it's 4:30AM so I should sleep.
I'll do this when I next have some time. Probably in 2-3 days. It'll probably be under the #147237 PR. |
Proposed change
Based on feedback for #147217 I have split my contribution into multiple smaller PRs. This is PR 1/6.
This PR:
Refactors the Songpal integration to use a coordinator for data update tasks. Maintains backwards compatibility.
Adds me to codeowners list since the refactoring is substantial and I am willing to provide ongoing support for it.
Overall changes: (across all 6 PRs)
With these changes, additional entities are created to expose enum, boolean, string, and integer settings based on whatever the Songpal device supports.
My motivation for making this was because I wanted to be able to control night mode, voice enhancement, and subwoofer level from Home Assistant.
Before:

After:

The additional entities may differ from device to device, so are generated dynamically based on what python-songpal reports as available.
(I don't expose playback_settings because these settings are duplicated for each input source, which seemed a bit messy. I also don't expose speaker_settings because it was giving me an Invalid Argument error and so was impossible for me to test with my device.)
I believe this change maintains backwards compatibility (existing entity unique IDs should not change, existing services are conserved).
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: