Growatt Inverter Mode Switch

TL;DR - Provide switches through the GW API (if it’s possible) to change the hybrid inverter mode.

My Growatt hybrid inverter (in a PV + battery setup) has the option to change between 3 modes.

  1. Grid First - where the system prioritises export before sending to battery/house
  2. Battery First - where the system prioritises the charging of batteries before anything else.
  3. Load First - where the system prioritises supplying the house before charging the batteries or exporting to the grid.

In Battery First mode, you can set the windows of time when you’d like the batteries to be charged. This setup is really useful with flexible electricity tariffs or when there are overnight windows of cheap energy.

If the Gw HA integration had the ability to dynamically switch between these modes (and read the battery level), you could optimise the overnight charge based on the projected energy production of the following day. Ie. if you’re expecting cloud+rain, you may wish to charge overnight. If you’re expecting a sunny day, you would set the mode to Load First so that the battery can charge on the excess energy produced during the day.

@indykoning @muppet3000 - is there a plan to merge in PyPi_GrowattServer v1.2.0? I think this covers my feature request and more.

Hi Mark,
Did you find a solution to this?
I have Growatt SPH3600 and trying to solve the same problem.

Best wishes,
Kasper

I did! Although it’s in a few different pieces and involves some AppDaemon apps to trigger the python code. I’m not by the computer until tomorrow so will post something as soon as I can.

I cobbled something together that works, but would be good to get some feedback. AppDaemon feels a bit alien to me and I’m not sure I’ve done it efficiently enough.

That’s great!
I’ll be happy to give some feedback on this.

Hello,

I’ve published my “rough and ready” code to github.

I dont have a lot of knowledge on AppDaemon, so I’ve structured it as separate apps. I think this could be consolidated into a single file. I’m just not sure how - i’m not a professional coder.

As far as I can tell, you cant change the mode as I requested in the original post. Instead you need to set a time window within which the batteries will charge / discharge. This is exactly the same as how the growatt server works.

The growattServer file has an extra function in it to read the mix inverter settings (related to getting the charge / discharge times). This is not on the original branch. This is copied locally to the AD folder and is imported from there.

Unfortunately, I dont have a lot of time to add a lot of commentary tonight, but I’ll try to get to it over the weekend. I’m keen to tidy things up in my code too.

Good luck - and be sure to read my disclaimer. I accept no responsibility for any damage, problems or issues that arise with your Growatt system as a result of its use :slight_smile:

mjdyson/ad-growatt (github.com)

1 Like

Last thing…

This code is very much a proof of concept for me - settings can be read and written. I will be working on it over the coming weeks to refine further.

The server is also sometimes slow and I cant figure out if it’s the code that fails, or if it’s just the website not responding.

1 Like

A workaround for the missing mode change in growatt could be to set the time window to 00:00 - 23:59 and just turn that on and off.
I’m traveling the next days and will test it in the weekend.

That could be good. Although I also like the idea of giving it a failsafe that it controls itself. The API doesn’t always seem to respond first time, so im going to look at processing the response codes. It may be useful to supply a command status / retry routine if it keeps failing.

Firstly: Its my first dab in this area. I have made all my automation and logic in node-red, so I’m trying to find my way here.

I got appdeamon running and your scripts copied in:
2022-05-26 19:32:28.331612 INFO AppDaemon: App 'set_discharge_rate' added 2022-05-26 19:32:28.333537 INFO AppDaemon: App 'set_charge_rate' added 2022-05-26 19:32:28.334757 INFO AppDaemon: App 'get_discharge_rate' added

A couple of warnings:
2022-05-26 19:32:30.360612 WARNING AppDaemon: No app description found for: /config/appdaemon/apps/growattServer.py - ignoring
2022-05-26 19:32:30.375042 WARNING set_discharge_rate: set_discharge_rate: Entity input_button.house_battery_set_discharge_rate_button not found in namespace default
2022-05-26 19:32:30.377569 WARNING set_charge_rate: set_charge_rate: Entity input_button.house_battery_set_charge_rate_button not found in namespace default
2022-05-26 19:32:30.380032 WARNING get_discharge_rate: get_discharge_rate: Entity input_button.house_battery_get_discharge_rate_button not found in namespace default

Seems Im missing the entity input_button.house_battery_get_discharge_rate_button. Is that a helper or part of some yaml you have created in home-assistant?

My biggest problem is that I don’t know how to access the functionality in the scripts. Im guessing that the scripts can be called from home-assistant with an argument and execute the command on the server and return some sort of status.

How have you integrated the functionality in home-assistant?

I added three helper buttons:

  • house_battery_set_discharge_rate_button
  • house_battery_set_charge_rate_button
  • house_battery_get_discharge_rate_button

And appdeamon starts without errors :smile:
So far so good - now off to figure out how to get the apps to execute. Any pointer?

When I get I running, I will do a little write up to ease the process for others.

Great work!

I’ll write some of this up on GitHub this weekend too! Finally my working week is done!

So, how I’ve got the code to run is for AD to have a listener within the initialize function to watch for the change of state of a button entity in HA. I chose to do this via an input button helper. I’ve got three of these, as I’ve been playing with different ways of doing a UI for it. I’m not sure all of these will stick.

This bit of the code is the listener. Also note that self.get_inverter_settings is what’s triggered when the button is pressed.

def initialize(self):
    self.listen_state(self.get_inverter_settings, "input_button.house_battery_get_discharge_rate_button")

Similarly in reverse, when data comes back to self.response i’ve used a template to pull out the value and put it into a sensor state. If I remember correctly, these sensors are created by AD, so they will not exist until they’re filled with data. I should probably convert these to normal sensors / helpers and set the state of those instead.

self.set_state("sensor.battery_charge_time1_start", state = self.response['obj']['mixBean']['forcedChargeTimeStart1'], attributes = {"friendly_name": "Charge Time 1 Start"})

It took me a while to find the right way to find the right template for the JSON, but it helped to call the code from a python3 prompt in bash and see the raw output. The more basic examples on the original server code were helpful on this :slight_smile:

This is the type of UI I’ve been thinking about.

  1. List of sensors that HA knows about
  2. Easy time adjustment (maybe need a reset button to get back to my overnight rate). These are desired times and different to those mentioned in 1.
  3. Get settings toggle button to pull the latest data from the growatt server
  4. Push settings, will set new charge settings according to the desired time. I’m hoping to eventually toggle the enabled/disabled value every time push settings is pressed.
  5. A toggle for Max SoC charge, which changes by 10% (from 60-100%) every time it’s pressed.

Great work! Thanks a lot!!!

I got it working after adding the missing helpers:

Helper datetime:
growatt_start_time

Helper dropdown
input_select.growatt_charge_final_soc
(add relevant charge percentages)

Helper boolean
growatt_force_charge_on

My plan is to create some logic that evaluates the estimated solar production and energy price and charge based on that. eg: If solar < XX kwHr AND energy price < YY THEN charge. This will need a bit of thought to work well.
I have a sensor that picks up prices from NordPool by the hour. This would then run full auto once every hour.

This is really good :smile: Here is my first iteration on controlling it. I’m using Node-Red. Interface looks like this:
image

Grid charge enabled is just to monitor the state on the server - updated when state is changed.
Charge_Battery is a manual switch and also an indicator of the state. When switched it calls “growatt_force_charge_on” waits 5 sec and then calls “house_battery_get_discharge_rate_button”.
Charge Time 1 Start = 00:00
Charge Time 1 Stop = 23:59
This allows node-red and home-assistant to run the timer logic and I only need to call “growatt_force_charge_on” to start charging.

Charge_timer_on_off turns the timer automation on and off
Start and Stop sliders are used to set time on a simple hour basis. Electricity prices are set per hour on the NordPool Exchange and the sliders are the fastest way.

Charge automation will be used to turn on/off automatic charge control based on the lowest day price - but I’m not there yet.

To work it needs the following helpers in HA:
Charge_end_time - number, slider
Charge_start_time - number, slider
charge_timer_on_off - toggle
charge_automation_on_off - toggle

Flow from node-red:

[{"id":"a8b6b9981e2da679","type":"tab","label":"Charger","disabled":false,"info":"","env":[]},{"id":"d6182880cb2b7ae9","type":"comment","z":"a8b6b9981e2da679","name":"Turning Growatt AC chage on/off","info":"","x":130,"y":40,"wires":[]},{"id":"f4e21e20c9ebca29","type":"inject","z":"a8b6b9981e2da679","name":"Inject time","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"60","crontab":"","once":false,"onceDelay":"","topic":"CurrentTime","payloadType":"date","x":130,"y":320,"wires":[["90d13b8b5340cee0"]]},{"id":"fa09d576d633a447","type":"switch","z":"a8b6b9981e2da679","name":"","property":"payload","propertyType":"msg","rules":[{"t":"eq","v":"start_charge_time","vt":"global"},{"t":"eq","v":"stop_charge_time","vt":"global"}],"checkall":"true","repair":false,"outputs":2,"x":730,"y":320,"wires":[["87319ace7b99bdee"],["a420c3f4803b23dc"]]},{"id":"90d13b8b5340cee0","type":"moment","z":"a8b6b9981e2da679","name":"","topic":"","input":"","inputType":"msg","inTz":"Europe/Copenhagen","adjAmount":0,"adjType":"days","adjDir":"add","format":"HH","locale":"C","output":"","outputType":"msg","outTz":"Europe/Copenhagen","x":360,"y":320,"wires":[["96ea7351eddf8dac"]]},{"id":"d62edeaeb38c7ca8","type":"server-state-changed","z":"a8b6b9981e2da679","name":"læs starttid fra ui","server":"785585e5.21e4ec","version":4,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":"input_number.charge_start_time","entityidfiltertype":"exact","outputinitially":true,"state_type":"str","haltifstate":"","halt_if_type":"str","halt_if_compare":"is","outputs":1,"output_only_on_state_change":true,"for":0,"forType":"num","forUnits":"minutes","ignorePrevStateNull":false,"ignorePrevStateUnknown":false,"ignorePrevStateUnavailable":false,"ignoreCurrentStateUnknown":false,"ignoreCurrentStateUnavailable":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"","valueType":"triggerId"}],"x":120,"y":140,"wires":[["3bb55aeb5ebc2696"]]},{"id":"e8982f41a2133138","type":"server-state-changed","z":"a8b6b9981e2da679","name":"læs sluttid fra ui","server":"785585e5.21e4ec","version":4,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":"input_number.charge_end_time","entityidfiltertype":"exact","outputinitially":true,"state_type":"str","haltifstate":"","halt_if_type":"str","halt_if_compare":"is","outputs":1,"output_only_on_state_change":true,"for":0,"forType":"num","forUnits":"minutes","ignorePrevStateNull":false,"ignorePrevStateUnknown":false,"ignorePrevStateUnavailable":false,"ignoreCurrentStateUnknown":false,"ignoreCurrentStateUnavailable":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"","valueType":"triggerId"}],"x":120,"y":200,"wires":[["c23ad9a67ba7e56d"]]},{"id":"6c7915f3c4e7f614","type":"debug","z":"a8b6b9981e2da679","name":"1","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":510,"y":940,"wires":[]},{"id":"a14e4f8dba1a205b","type":"change","z":"a8b6b9981e2da679","name":"","rules":[{"t":"set","p":"stop_charge_time","pt":"global","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":620,"y":200,"wires":[[]]},{"id":"dd64ebc25c40287d","type":"change","z":"a8b6b9981e2da679","name":"","rules":[{"t":"set","p":"start_charge_time","pt":"global","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":620,"y":140,"wires":[[]]},{"id":"96ea7351eddf8dac","type":"api-current-state","z":"a8b6b9981e2da679","name":"Is timer on?","server":"785585e5.21e4ec","version":3,"outputs":2,"halt_if":"on","halt_if_type":"str","halt_if_compare":"is","entity_id":"input_boolean.charge_timer_on_off","state_type":"str","blockInputOverrides":false,"outputProperties":[],"for":0,"forType":"num","forUnits":"minutes","override_topic":false,"state_location":"payload","override_payload":"msg","entity_location":"data","override_data":"msg","x":570,"y":320,"wires":[["fa09d576d633a447"],[]]},{"id":"e9de28bfaa953647","type":"debug","z":"a8b6b9981e2da679","name":"3","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":690,"y":1000,"wires":[]},{"id":"6b4758c88b69ce4d","type":"debug","z":"a8b6b9981e2da679","name":"4","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":490,"y":1000,"wires":[]},{"id":"81d97675c4bf33df","type":"api-call-service","z":"a8b6b9981e2da679","name":"","server":"785585e5.21e4ec","version":5,"debugenabled":false,"domain":"input_button","service":"press","areaId":[],"deviceId":[],"entityId":["input_button.house_battery_set_charge_rate_button"],"data":"","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":830,"y":480,"wires":[["11121fb5668172ad"]]},{"id":"2c7185e6a72d2cc5","type":"delay","z":"a8b6b9981e2da679","name":"","pauseType":"delay","timeout":"1","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":660,"y":480,"wires":[["81d97675c4bf33df"]]},{"id":"3e6fecfa13e474f0","type":"api-call-service","z":"a8b6b9981e2da679","name":"","server":"785585e5.21e4ec","version":5,"debugenabled":false,"domain":"input_boolean","service":"turn_on","areaId":[],"deviceId":[],"entityId":["input_boolean.growatt_force_charge_on"],"data":"","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":420,"y":440,"wires":[["2c7185e6a72d2cc5"]]},{"id":"d628ed899738498e","type":"api-call-service","z":"a8b6b9981e2da679","name":"","server":"785585e5.21e4ec","version":5,"debugenabled":false,"domain":"input_boolean","service":"turn_off","areaId":[],"deviceId":[],"entityId":["input_boolean.growatt_force_charge_on"],"data":"","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":420,"y":500,"wires":[["2c7185e6a72d2cc5"]]},{"id":"3bb55aeb5ebc2696","type":"function","z":"a8b6b9981e2da679","name":"afrunding","func":"msg.payload = msg.payload >> 0\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":360,"y":140,"wires":[["dd64ebc25c40287d"]]},{"id":"c23ad9a67ba7e56d","type":"function","z":"a8b6b9981e2da679","name":"afrunding","func":"msg.payload = msg.payload >> 0\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":360,"y":200,"wires":[["a14e4f8dba1a205b"]]},{"id":"1d099ff23d9c61c0","type":"api-call-service","z":"a8b6b9981e2da679","name":"Update status","server":"785585e5.21e4ec","version":5,"debugenabled":false,"domain":"input_button","service":"press","areaId":[],"deviceId":[],"entityId":["input_button.house_battery_get_discharge_rate_button"],"data":"","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":1160,"y":480,"wires":[[]]},{"id":"11121fb5668172ad","type":"delay","z":"a8b6b9981e2da679","name":"","pauseType":"delay","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":1000,"y":480,"wires":[["1d099ff23d9c61c0"]]},{"id":"4493dbd66a8e0d12","type":"ha-entity","z":"a8b6b9981e2da679","name":"Charge_Battery","server":"785585e5.21e4ec","version":2,"debugenabled":false,"outputs":2,"entityType":"switch","config":[{"property":"name","value":"Charge_Battery"},{"property":"device_class","value":""},{"property":"icon","value":"mdi:battery-charging-100"},{"property":"unit_of_measurement","value":""},{"property":"state_class","value":""},{"property":"last_reset","value":""}],"state":"payload","stateType":"msg","attributes":[],"resend":true,"outputLocation":"payload","outputLocationType":"none","inputOverride":"allow","outputOnStateChange":true,"outputPayload":"","outputPayloadType":"jsonata","x":180,"y":460,"wires":[["3e6fecfa13e474f0"],["d628ed899738498e"]]},{"id":"87319ace7b99bdee","type":"api-call-service","z":"a8b6b9981e2da679","name":"","server":"785585e5.21e4ec","version":5,"debugenabled":false,"domain":"switch","service":"turn_on","areaId":[],"deviceId":[],"entityId":["switch.charge_battery"],"data":"","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":940,"y":300,"wires":[[]]},{"id":"a420c3f4803b23dc","type":"api-call-service","z":"a8b6b9981e2da679","name":"","server":"785585e5.21e4ec","version":5,"debugenabled":false,"domain":"switch","service":"turn_off","areaId":[],"deviceId":[],"entityId":["switch.charge_battery"],"data":"","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":940,"y":340,"wires":[[]]},{"id":"e3159f9d7f5a6481","type":"comment","z":"a8b6b9981e2da679","name":"Timer - read from UI using helpers","info":"","x":180,"y":100,"wires":[]},{"id":"6ff6e2a9a06da8a5","type":"comment","z":"a8b6b9981e2da679","name":"Timer automation - checks every minut","info":"","x":190,"y":280,"wires":[]},{"id":"925b1977f3f9d746","type":"comment","z":"a8b6b9981e2da679","name":"Switch - manual and called from timer","info":"","x":190,"y":400,"wires":[]},{"id":"a9642f818802c937","type":"comment","z":"a8b6b9981e2da679","name":"Automatic charging logic (in progress)","info":"","x":190,"y":680,"wires":[]},{"id":"2a24649745724576","type":"inject","z":"a8b6b9981e2da679","name":"Inject time","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"60","crontab":"","once":false,"onceDelay":"","topic":"CurrentTime","payloadType":"date","x":150,"y":780,"wires":[["c59586f817628d23"]]},{"id":"c59586f817628d23","type":"api-current-state","z":"a8b6b9981e2da679","name":"Is charge automation on?","server":"785585e5.21e4ec","version":3,"outputs":2,"halt_if":"on","halt_if_type":"str","halt_if_compare":"is","entity_id":"input_boolean.charge_automation_on_off","state_type":"str","blockInputOverrides":false,"outputProperties":[],"for":0,"forType":"num","forUnits":"minutes","override_topic":false,"state_location":"payload","override_payload":"msg","entity_location":"data","override_data":"msg","x":370,"y":780,"wires":[["fe9c3ea0432642f3"],[]]},{"id":"fe9c3ea0432642f3","type":"api-current-state","z":"a8b6b9981e2da679","name":"Todays solar < 10 kwh","server":"785585e5.21e4ec","version":3,"outputs":2,"halt_if":"10","halt_if_type":"num","halt_if_compare":"lte","entity_id":"sensor.energy_production_today","state_type":"str","blockInputOverrides":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"entity"}],"for":"0","forType":"num","forUnits":"minutes","override_topic":false,"state_location":"payload","override_payload":"msg","entity_location":"data","override_data":"msg","x":620,"y":780,"wires":[[],[]]},{"id":"fe800241f1368faa","type":"comment","z":"a8b6b9981e2da679","name":"To be:logic for picking low price time","info":"","x":960,"y":780,"wires":[]},{"id":"74477df3f7b1be7d","type":"comment","z":"a8b6b9981e2da679","name":"Calls Charge_Battery in next line","info":"","x":1170,"y":320,"wires":[]},{"id":"e47ed72ef7f5e02c","type":"debug","z":"a8b6b9981e2da679","name":"2","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":690,"y":940,"wires":[]},{"id":"785585e5.21e4ec","type":"server","name":"Home Assistant","version":1,"addon":true,"rejectUnauthorizedCerts":true,"ha_boolean":"y|yes|true|on|home|open","connectionDelay":false,"cacheJson":true}]

An addition to the interface and function: Show energy prices and control charging based on price. Charge automation has to be on for price based charging to start.

.

It seems the new growatt status is not always returned, even with a 5 sec delay. Im considering a loop that checks that status and press update if it seems wrong.

1 Like

Hello
How did you integrated te inverter mode switch in HA? Is there a link to the documentation or description?
This is really the solution I’ve been looking for for 6 months.
I can get into a pilot project with dynamic pricing and this piece was still missing.
Thanks

Functionality is certainly possible, however it is somewhat more complicated than a nice integration would provide. It also needs a bit more thought than a simple switch (i.e. time periods need to be included). It also doesn’t work when the Growatt server is down.

If you have a read of above, Kasper and I have set out some hints, however there is some coding involved to meet your own needs.

This is great!

Just started to work with my hybrid inverter+battery and Home Assistant and this is close to what i want.

Did a few templates to check if current hour has lowest (or second, third,… lowest) price Nordpool.
Also to see if the difference between lowest<>highest price is big enough to charge and discharge based on this data.

But i have no integration to execute the Growatt schedule, maby this can help me. I will give it a try if there is no HA integration to use straight off?

And, do you have a complete example of how you did that UI in HA? I now can run python scripts, but that’s where it stop :slight_smile:

Hi Kasper

How did you get the data from the data from Growatt → AppDeamon → Home Assistant?