Skip to content

Add services for managing Time-of-Use (TOU) schedule for Growatt integration#154703

Merged
joostlek merged 28 commits into
home-assistant:devfrom
johanzander:growatt-services
Dec 16, 2025
Merged

Add services for managing Time-of-Use (TOU) schedule for Growatt integration#154703
joostlek merged 28 commits into
home-assistant:devfrom
johanzander:growatt-services

Conversation

@johanzander
Copy link
Copy Markdown
Contributor

@johanzander johanzander commented Oct 17, 2025

Proposed change

Adds two new services to the Growatt Server integration for managing Time-of-Use
(TOU) battery scheduling on MIN inverters:

  • growatt_server.update_time_segment: Configure individual time segments (1-9)
    with battery operation mode, time range, and enable/disable state
  • growatt_server.read_time_segments: Read current configuration of all 9 time
    segments

These services enable users to automate their battery charging/discharging schedules
based on electricity rates, solar generation, or other factors.

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

  • I understand the code I am submitting and can explain how it works.
  • 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.
  • Any generated code has been carefully reviewed for correctness and compliance with project standards.

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:

@johanzander
Copy link
Copy Markdown
Contributor Author

@joostlek , any chance you can have a look at this one?

Comment thread homeassistant/components/growatt_server/__init__.py Outdated
Comment thread homeassistant/components/growatt_server/__init__.py Outdated
Comment thread homeassistant/components/growatt_server/coordinator.py Outdated
Comment thread homeassistant/components/growatt_server/coordinator.py Outdated
Comment thread homeassistant/components/growatt_server/services.yaml Outdated
Comment thread homeassistant/components/growatt_server/services.yaml Outdated
@home-assistant home-assistant Bot marked this pull request as draft October 25, 2025 20:07
@home-assistant
Copy link
Copy Markdown
Contributor

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.

- Update fixture names from mock_growatt_api to mock_growatt_v1_api/mock_growatt_classic_api
- Fix min_write_time_segment mock to accept variable arguments
- Use correct Classic API fixture for non-MIN device test
Rename services from update_min_time_segment/read_min_time_segments
to update_time_segment/read_time_segments to support future expansion
to other inverter types beyond MIN devices.
- Fix bare Exception catches in service handlers, replace with specific API exceptions
- Remove debug logging leftover from development
- Add comprehensive error handling tests for services
- Improve test coverage for service error paths
- All 42 tests passing with 85% overall coverage
@johanzander johanzander marked this pull request as draft October 30, 2025 20:29
@johanzander johanzander marked this pull request as ready for review November 3, 2025 20:41
Copy link
Copy Markdown
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.

Okay I need to look at this when I am not about to head to bed but I think this is a simple thing you can add to make the tests better :)

Comment thread tests/components/growatt_server/test_services.py Outdated
@johanzander
Copy link
Copy Markdown
Contributor Author

johanzander commented Nov 4, 2025

@joostlek - I need guidance on a topic.
I gave the services methods generic names:
growatt_server.update_time_segment and growatt_server.read_time_segments to future proof these and reuse them for other types of inverters. Now I suspect that the calls might be specific for min inverters. There is support for mix inverters on the way in the library and they would use slightly different API call, which would quality for another service call, unique for those. Example from the library:

        charge_params = api.MixAcChargeTimeParams(
            charge_power=80,           # 80% charging power
            charge_stop_soc=95,        # Stop at 95% SOC
            mains_enabled=True,        # Enable mains charging
            start_hour=14,             # Start at 14:00
            start_minute=0,
            end_hour=16,               # End at 16:00
            end_minute=0,
            enabled=True
        )
        result = api.write_parameter(
            device_sn=device_sn,
            device_type=growattServer.DeviceType.SPH_MIX,
            command="mix_ac_charge_time_period",
            params=charge_params
        )

This would be translated into something like growatt_server.update_charge_time_period in the growatt integration

Question: should we prefix the services, so

growatt_server.update_min_time_segment 
growatt_server.read_min_time_segments 

and

growatt_server.update_mix_charge_time_period
growatt_server.update_mix_discharge_time_period

or

growatt_server.update_time_segment 
growatt_server.read_time_segments 
growatt_server.update_charge_time_period
growatt_server.update_discharge_time_period

WDYT? Want to avoid a breaking change and get it right first time...

@joostlek
Copy link
Copy Markdown
Member

joostlek commented Nov 5, 2025

I'm on mobile now (I'm on holiday and I was reviewing this yesterday (or at least trying to) because I was bored). But I would need to check what the current parameters were.

In a way I would say that making the service generic enough to be used by both devices makes sense (as then blueprints and automations can be shared with a broader audience). But if the input parameters are drastically different then that probably doesn't make much sense.

So that would be my first preference, other than that, there are quite a number of growatt models, do we have any vision on if other inverters get the same? Or if they don't have it, do they carry the same type of parameters?

If the set of parameters in this PR is only specific for time segments and the one you are talking now is specific for charging ours, then it would make sense to have specific names for them, if we expect every model to have a wildly different set of parameters, having the model name in there would probably make sense (but now that I type this it probably isn't ideal either as you'd have "update segments (min)" "update segments (mix)" and whatnot, so when automating it can be harder to recognise what service to use when.

In any case, thanks for sharing your concern! I'll try to get back to this after I recover from my massive jet lag that I will have next weekend. Also, please don't forget to send me a message on discord so we can add you to the appropriate channels as codeowner :)

@johanzander
Copy link
Copy Markdown
Contributor Author

johanzander commented Nov 6, 2025

@joostlek - After checking all the different Growatt inverter APIs, I've found that there are 4 different ways they handle time-of-use/charge/discharge controls. Each type works completely differently.

The 4 Types

MIN Inverters - Time Segments

  • 9 programmable time slots
  • 3 modes: Load First, Battery First, Grid First
  • No charge/discharge power settings (handled by separate number entities)

NOAH Inverters - Basic Time Segments

  • 9 programmable time slots (like MIN)
  • Only 2 modes: Load First, Battery First (no Grid First)
  • Has output power limit setting per time segment

MIX(SPH)/SPA Inverters - Charge/Discharge Periods

  • Separate charge and discharge controls
  • 3 time periods for each
  • Power percentage controls, SOC limits, global on/off
  • MIX and SPA work exactly the same

STORAGE Inverters - Basic Periods

  • Separate charge and discharge controls (like MIX)
  • Only 1 time period for each
  • Just simple on/off, no advanced settings

Propsed Service Names

Main models get clean names:

MIN

  • growatt_server.update_time_segment

MIX/SPA

  • growatt_server.update_charge_periods
  • growatt_server.update_discharge_periods

Niche models get "basic_" prefix:

NOAH

  • growatt_server.update_basic_time_segment

STORAGE

  • growatt_server.update_basic_charge_period
  • growatt_server.update_basic_discharge_period

Why This Makes Sense

  • To the best of my knowledge, MIN and MIX inverters seem to be the newer more modern inverters with more advanced controls.
  • NOAH and STORAGE are specialty products, so the "basic_" prefix makes it clear they're different
  • Possible to create simple Blueprints based on these 4 different control types
  • If a new inverter type is release, it can reuse any of the existing services (if parameters are same)

So to summarize, the service in this PR is unique for the min inverters, but I still think the naming makes sense.

@johanzander
Copy link
Copy Markdown
Contributor Author

Any thoughts on this @joostlek ?

@GraemeDBlue
Copy link
Copy Markdown

@joostlek - I need guidance on a topic. I gave the services methods generic names: growatt_server.update_time_segment and growatt_server.read_time_segments to future proof these and reuse them for other types of inverters. Now I suspect that the calls might be specific for min inverters. There is support for mix inverters on the way in the library and they would use slightly different API call, which would quality for another service call, unique for those. Example from the library:

        charge_params = api.MixAcChargeTimeParams(
            charge_power=80,           # 80% charging power
            charge_stop_soc=95,        # Stop at 95% SOC
            mains_enabled=True,        # Enable mains charging
            start_hour=14,             # Start at 14:00
            start_minute=0,
            end_hour=16,               # End at 16:00
            end_minute=0,
            enabled=True
        )
        result = api.write_parameter(
            device_sn=device_sn,
            device_type=growattServer.DeviceType.SPH_MIX,
            command="mix_ac_charge_time_period",
            params=charge_params
        )

This would be translated into something like growatt_server.update_charge_time_period in the growatt integration

Question: should we prefix the services, so

growatt_server.update_min_time_segment 
growatt_server.read_min_time_segments 

and

growatt_server.update_mix_charge_time_period
growatt_server.update_mix_discharge_time_period

or

growatt_server.update_time_segment 
growatt_server.read_time_segments 
growatt_server.update_charge_time_period
growatt_server.update_discharge_time_period

WDYT? Want to avoid a breaking change and get it right first time...

I think its easier for the library to handle the differences and the integration just use the same methods but I added these interfaces https://github.com/indykoning/PyPi_GrowattServer/pull/125/files#diff-12865f9986e5f28194397aae0efed2b7abc9137d18c0a09197682e6c7e65cd01R156 and which i had used like so https://github.com/johanzander/growatt_server_upstream/pull/7/files#diff-33680c02b8a262be8cbd5f7298d8434e770d9fac3944865a872259b2ba23f96eR217 or https://github.com/johanzander/growatt_server_upstream/pull/7/files#diff-33680c02b8a262be8cbd5f7298d8434e770d9fac3944865a872259b2ba23f96eR289 as example

@johanzander
Copy link
Copy Markdown
Contributor Author

@GraemeDBlue - for sensors, numbers and switches, I agree the integration would benefit from using the "unified" V1 methods from the library. But for actions/services - based on what my quick analysis, the control mechanisms are quite different. Or do you mean that if would be possible to have a unified service for all inverters (MIN,MIX, etc.)? What parameters would it have in that case?

@GraemeDBlue
Copy link
Copy Markdown

@johanzander so I had the idea to create different versions in service.yaml for each type, with all the correct parameters to match and then depending on which type you have it would only load the relevant one. I have it working on my own branch like that, Im on my phone atm, otherwise I would show you my example code.

@johanzander
Copy link
Copy Markdown
Contributor Author

yes, that it basically what I propose too. but not to prefix the inverter API type in the methods name, but name them for what they do (see above) - which is also what they are named in the PR.

Another strong argument for this is because there is no logic on which inverter types use which API type. I am about to update the documentation with which inverters use the MIN API, and it is a mix of MIC, MIN, MOD and MID: home-assistant/home-assistant.io#41713.

If we would name the method min_update_time_segment, that would be very confusing for users of MIC, MOD and MID inverters...

@johanzander
Copy link
Copy Markdown
Contributor Author

@joostlek - would appreciate merge or feedback on this PR (hopefully the former :-))!

@bartbutenaers
Copy link
Copy Markdown

Hi @johanzander, @joostlek
Sorry for the explicit mentioning!!! But I really hope you guys can find some kind of agreement about the method naming. Because it would be a pity if such a nice pull request would end up dying here a slow and painful dead 😉
Thanks both for all the hard work you do for us!!

@johanzander
Copy link
Copy Markdown
Contributor Author

Ready from my side, awaiting feedback from @joostlek !

@joostlek
Copy link
Copy Markdown
Member

Don't worry, the first step is just doing our due diligence. I haven't found time to get back to reviewing this PR yet

Comment on lines +55 to +62
BATT_MODE_MAP = {
"load-first": BATT_MODE_LOAD_FIRST,
"0": BATT_MODE_LOAD_FIRST,
"battery-first": BATT_MODE_BATTERY_FIRST,
"1": BATT_MODE_BATTERY_FIRST,
"grid-first": BATT_MODE_GRID_FIRST,
"2": BATT_MODE_GRID_FIRST,
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I believe this isn't used

Comment thread homeassistant/components/growatt_server/const.py Outdated
@joostlek joostlek merged commit 1709a9d into home-assistant:dev Dec 16, 2025
36 checks passed
heindrichpaul pushed a commit to heindrichpaul/core that referenced this pull request Dec 17, 2025
…gration (home-assistant#154703)

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
@github-actions github-actions Bot locked and limited conversation to collaborators Dec 17, 2025
@johanzander johanzander deleted the growatt-services branch December 18, 2025 12:39
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants