Skip to content
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 solar charging options to Wallbox integration #139286

Open
wants to merge 16 commits into
base: dev
Choose a base branch
from

Conversation

jorisdrenth
Copy link
Contributor

@jorisdrenth jorisdrenth commented Feb 25, 2025

Proposed change

Wallbox has an option in their app to charge a car using energy from a users solar panels. In the Wallbox app this can be turned off, or either one of two modes can be selected: eco mode, which starts charging at 2A and fills with energy from the net till 6A, or full solar, which starts charging at 6A energy from solar panels. A Wallbox Load balancer is needed for this functionality.

This PR adds an "Solar charging" entity to the Wallbox integration. It features a select with three options: "Off", "Eco mode" or "Full solar". This way the solar charging mode can be controlled via Home Assistant. Entity availability is checked with the Power Boost availability (Wallbox' confusing name for the load balancer).

For the API communication I also opened a PR to the Wallbox python project: cliviu74/wallbox#63. The merging of that PR is blocking for this PR.

Disclaimer: it's been 5 year since I worked with Python and this is my first PR for Home Assistant. Looking forward to receive feedback in a constructive way.

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

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:

Sorry, something went wrong.

Copy link

@home-assistant home-assistant bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @jorisdrenth

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!

@home-assistant
Copy link

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

Code owner commands

Code owners of wallbox 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 wallbox 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.

@jorisdrenth
Copy link
Contributor Author

PR cliviu74/wallbox#63 has been merged so I'll open this PR for review.

@jorisdrenth jorisdrenth marked this pull request as ready for review February 28, 2025 18:49
@home-assistant home-assistant bot marked this pull request as draft February 28, 2025 23:20
@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.

@jorisdrenth jorisdrenth mentioned this pull request Feb 28, 2025
19 tasks
@jorisdrenth jorisdrenth marked this pull request as ready for review February 28, 2025 23:59
@home-assistant home-assistant bot requested a review from abmantis February 28, 2025 23:59
@hesselonline
Copy link
Contributor

@jorisdrenth code looks fine, but please add sufficient tests for the new component.

@jorisdrenth
Copy link
Contributor Author

@hesselonline I've added tests, based on the test_switch.py test and select test I've found in another component folder. It was kind of a steep learning curve so I'm not sure whether it makes any sense. Can you take a look at 1a3cd1c?

@jorisdrenth jorisdrenth force-pushed the wallbox-solar-charging branch from 1a3cd1c to a35843a Compare March 1, 2025 14:27
@hesselonline
Copy link
Contributor

@hesselonline I've added tests, based on the test_switch.py test and select test I've found in another component folder. It was kind of a steep learning curve so I'm not sure whether it makes any sense. Can you take a look at 1a3cd1c?

looks good on first glance, can you check the code coverage of these tests (wallbox integration should have 100% code cov)?

@jorisdrenth
Copy link
Contributor Author

@hesselonline Ah thanks for pointing that out, that's a nice feature. I've added extra tests:

---------- coverage: platform linux, python 3.13.1-final-0 -----------
Name                                              Stmts   Miss  Cover   Missing
-------------------------------------------------------------------------------
homeassistant/components/wallbox/__init__.py         25      0   100%
homeassistant/components/wallbox/config_flow.py      38      0   100%
homeassistant/components/wallbox/const.py            62      0   100%
homeassistant/components/wallbox/coordinator.py     121      4    97%   178-181
homeassistant/components/wallbox/entity.py           10      0   100%
homeassistant/components/wallbox/lock.py             32      0   100%
homeassistant/components/wallbox/number.py           49      0   100%
homeassistant/components/wallbox/select.py           31      0   100%
homeassistant/components/wallbox/sensor.py           39      0   100%
homeassistant/components/wallbox/switch.py           28      0   100%
-------------------------------------------------------------------------------
TOTAL                                               435      4    99%

The last percent are these lines. Do you have an idea how I can test them because it's just a value I set here in the test response?

@hesselonline
Copy link
Contributor

@hesselonline Ah thanks for pointing that out, that's a nice feature. I've added extra tests:

---------- coverage: platform linux, python 3.13.1-final-0 -----------
Name                                              Stmts   Miss  Cover   Missing
-------------------------------------------------------------------------------
homeassistant/components/wallbox/__init__.py         25      0   100%
homeassistant/components/wallbox/config_flow.py      38      0   100%
homeassistant/components/wallbox/const.py            62      0   100%
homeassistant/components/wallbox/coordinator.py     121      4    97%   178-181
homeassistant/components/wallbox/entity.py           10      0   100%
homeassistant/components/wallbox/lock.py             32      0   100%
homeassistant/components/wallbox/number.py           49      0   100%
homeassistant/components/wallbox/select.py           31      0   100%
homeassistant/components/wallbox/sensor.py           39      0   100%
homeassistant/components/wallbox/switch.py           28      0   100%
-------------------------------------------------------------------------------
TOTAL                                               435      4    99%

The last percent are these lines. Do you have an idea how I can test them because it's just a value I set here in the test response?

Yes, set 3 different values in test responses

@jorisdrenth
Copy link
Contributor Author

@hesselonline It has 100% code coverage now.

@jorisdrenth jorisdrenth force-pushed the wallbox-solar-charging branch from 59e9988 to 0c53ab7 Compare March 1, 2025 18:33
@jorisdrenth
Copy link
Contributor Author

@abmantis I think this needs your review before it can be merged?

Comment on lines 39 to 41
async_add_entities(
[WallboxSelect(coordinator, SELECT_TYPES[CHARGER_ECO_SMART_KEY])]
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since SELECT_TYPES is a dict, I kinda expected the code here to iterate over it (instead of hardcode picking an item from it.

Suggested change
async_add_entities(
[WallboxSelect(coordinator, SELECT_TYPES[CHARGER_ECO_SMART_KEY])]
)
async_add_entities(
[WallboxSelect(coordinator, description)]
for description in SELECT_TYPES
)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed this: 6fb9681

Your suggested code gave me an error because it only returns the key and not the value. I used the same coding style as other entities in the Wallbox component (see number.py and lock.py.

Note: in switch.py, which I used as base for the select.py file, it's also picking a value hardcoded. I'll fix that in a different PR.

super().__init__(coordinator)
self.entity_description = description
self._attr_unique_id = f"{description.key}-{coordinator.data[CHARGER_DATA_KEY][CHARGER_SERIAL_NUMBER_KEY]}"
self._attr_icon = "mdi:solar-power"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Icons ideally go into the icons translation file (icons.json)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check! d142208

Comment on lines 57 to 61
self._attr_options = [
EcoSmartMode.OFF,
EcoSmartMode.ECO_MODE,
EcoSmartMode.FULL_SOLAR,
]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These can be set in the entity description, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fixed too: jorisdrenth@7a025df

Comment on lines 69 to 71
in self.coordinator.data[CHARGER_DATA_KEY][CHARGER_PLAN_KEY][
CHARGER_FEATURES_KEY
]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm, is this is check we need to do before creating the entity? As in, this makes it sounds like this feature isn't always existing?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's right. Solar charging is only available if a Wallbox Load Balancer is installed, which reads other energy usage in the building by (in my case) reading data from the P1 meter. Your question makes me wonder: is a "exist" option available which I should use in stead of "available"?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jorisdrenth in the number component we do a try...except construction to check the user rights before adding the number component. Maybe you can do something similar before adding the select component. TLDR, move this check to the setup_entry, add only if the right charger features are present. (maybe combine this with the user rights check, becasue a limited Wallbox portal user will not be able to do this)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like this? 42b68ae

@property
def current_option(self) -> str | None:
"""Return the current selected option."""
return str(self.coordinator.data[CHARGER_SOLAR_CHARGING_MODE])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's implement a lambda in the entity description to handle this. Right now, the entity description dict is created to be extendable, but this method isn't (it is hardcoded to a single feature).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines 93 to 111
await hass.services.async_call(
SELECT_DOMAIN,
SERVICE_SELECT_OPTION,
{
ATTR_ENTITY_ID: MOCK_SELECT_ENTITY_ID,
ATTR_OPTION: EcoSmartMode.FULL_SOLAR,
},
blocking=True,
)

await hass.services.async_call(
SELECT_DOMAIN,
SERVICE_SELECT_OPTION,
{
ATTR_ENTITY_ID: MOCK_SELECT_ENTITY_ID,
ATTR_OPTION: EcoSmartMode.OFF,
},
blocking=True,
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above.

Comment on lines 136 to 154
await hass.services.async_call(
SELECT_DOMAIN,
SERVICE_SELECT_OPTION,
{
ATTR_ENTITY_ID: MOCK_SELECT_ENTITY_ID,
ATTR_OPTION: EcoSmartMode.ECO_MODE,
},
blocking=True,
)

await hass.services.async_call(
SELECT_DOMAIN,
SERVICE_SELECT_OPTION,
{
ATTR_ENTITY_ID: MOCK_SELECT_ENTITY_ID,
ATTR_OPTION: EcoSmartMode.OFF,
},
blocking=True,
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Samea as above

status_code=404,
)

with pytest.raises(ConnectionError):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Errors with services should raise a ConnectionError. They should have been caught and raise an translated HomeAssistantError instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

status_code=403,
)

with pytest.raises(ConfigEntryAuthFailed):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above

Comment on lines 187 to 196
with pytest.raises(ConnectionError):
await hass.services.async_call(
SELECT_DOMAIN,
SERVICE_SELECT_OPTION,
{
ATTR_ENTITY_ID: MOCK_SELECT_ENTITY_ID,
ATTR_OPTION: EcoSmartMode.FULL_SOLAR,
},
blocking=True,
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is really the same thing as above, as well as the one below. This means this test can be parameterized to reduce the repetitive code.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@home-assistant home-assistant bot marked this pull request as draft March 2, 2025 16:11
@jorisdrenth jorisdrenth requested a review from frenck March 8, 2025 23:47
@jorisdrenth jorisdrenth marked this pull request as ready for review March 8, 2025 23:50
Copy link
Contributor

@hesselonline hesselonline left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please move the checking for the right requirements from the 'available' property to the setup entry function.

Comment on lines 69 to 71
in self.coordinator.data[CHARGER_DATA_KEY][CHARGER_PLAN_KEY][
CHARGER_FEATURES_KEY
]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jorisdrenth in the number component we do a try...except construction to check the user rights before adding the number component. Maybe you can do something similar before adding the select component. TLDR, move this check to the setup_entry, add only if the right charger features are present. (maybe combine this with the user rights check, becasue a limited Wallbox portal user will not be able to do this)

@jorisdrenth
Copy link
Contributor Author

@frenck @hesselonline @abmantis Can you re-review this PR? I addressed all points of feedback.

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.

None yet

4 participants