Thanks to visit codestin.com
Credit goes to github.com

Skip to content

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

Draft
wants to merge 13 commits into
base: dev
Choose a base branch
from

Conversation

YoshiWalsh
Copy link

@YoshiWalsh YoshiWalsh commented Jun 20, 2025

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:
image

After:
image

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

  • Dependency upgrade
  • Bugfix (non-breaking change which fixes an issue)
  • New integration (thank you!)
  • New feature (which adds functionality to an existing integration)
  • Deprecation (breaking change to happen in the future)
  • Breaking change (fix/feature causing existing functionality to break)
  • Code quality improvements to existing code or addition of tests

Additional information

  • This PR fixes or closes issue:
  • This PR is related to issue:
  • Link to documentation pull request:
  • Link to developer documentation pull request:
  • Link to frontend pull request:

Checklist

  • The code change is tested and works locally.
  • Local tests pass. Your PR cannot be merged unless tests pass
  • There is no commented out code in this PR.
  • I have followed the development checklist
  • I have followed the perfect PR recommendations
  • The code has been formatted using Ruff (ruff format homeassistant tests)
  • Tests have been added to verify that the new code works.

If user exposed functionality or configuration variables are added/changed:

If the code communicates with devices, web services, or third-party tools:

  • The manifest file has all fields filled out correctly.
    Updated and included derived files by running: python3 -m script.hassfest.
  • New or updated dependencies have been added to requirements_all.txt.
    Updated by running python3 -m script.gen_requirements_all.
  • For the updated dependencies - a link to the changelog, or at minimum a diff between library versions is added to the PR description.

To help with the load of incoming pull requests:

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.
@home-assistant
Copy link

Hey there @rytilahti, @shenxn, mind taking a look at this pull request as it has been labeled with an integration (songpal) you are listed as a code owner for? Thanks!

Code owner commands

Code owners of songpal can trigger bot actions by commenting:

  • @home-assistant close Closes the pull request.
  • @home-assistant rename Awesome new title Renames the pull request.
  • @home-assistant reopen Reopen the pull request.
  • @home-assistant unassign songpal Removes the current integration label and assignees on the pull request, add the integration domain after the command.
  • @home-assistant add-label needs-more-information Add a label (needs-more-information, problem in dependency, problem in custom component) to the pull request.
  • @home-assistant remove-label needs-more-information Remove a label (needs-more-information, problem in dependency, problem in custom component) on the pull request.

@rytilahti
Copy link
Member

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.

@YoshiWalsh
Copy link
Author

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?

@rytilahti
Copy link
Member

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.

Copy link
Member

@joostlek joostlek left a 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

@home-assistant
Copy link

Please take a look at the requested changes, and use the Ready for review button when you are done, thanks 👍

Learn more about our pull request process.

@home-assistant home-assistant bot marked this pull request as draft June 20, 2025 15:33
Copy link
Member

@rytilahti rytilahti left a 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:
Copy link
Member

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?

Copy link
Author

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.

Comment on lines +208 to +211
if not volumes:
_LOGGER.error("Got no volume controls, bailing out")
self.available = False
return data
Copy link
Member

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?

Copy link
Author

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):
Copy link
Member

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

Copy link
Author

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.

Copy link
Member

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

Copy link
Author

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.

@joostlek
Copy link
Member

write that we should not create entities completely dynamically

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
@YoshiWalsh YoshiWalsh force-pushed the songpal-coordinator-refactor branch from 9dd41a5 to 46cc5bd Compare June 20, 2025 16:44
@YoshiWalsh YoshiWalsh force-pushed the songpal-coordinator-refactor branch from 02e3296 to 6aaaaa8 Compare June 20, 2025 17:51
@YoshiWalsh
Copy link
Author

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.

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.

I'll do this when I next have some time. Probably in 2-3 days. It'll probably be under the #147237 PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants