Hi All,
I figured I should share what I’ve been doing to script changes on my Growatt SPA3000 inverter as the current APIs seem to be limited and unstable.
I will try to keep this as simple as I can. I live in the UK with a 9kWh solar setup. I also have a SPA3000 with 2x 6.5 kWh batteries and like most solar people I’m currently with octopus energy as energy broker. Now Tariffs …. I’m on Flux which has a cheap night time rate between 02:00 to 05:00 and a super expensive period 16:00 to 19:00. In the spring/summer/autumn the solar and the battery will carry me through the expensive period on most days (I will come back to these exceptions), In the winter however I can get days with no sun or nowhere near enough. So I charge the batteries from the grid 02:00 to 05:00 to take advantage of the cheap rate. This is where the complication with the Flux tariff comes in, I need to keep 50% of the batteries charged for the peak evening period. So I set the “Load First > Discharge Stopped Soc” to 50% but I need to set “Load First > Discharge Stopped Soc” to 5% at 16:00 so I can use the stored energy during the expensive period, There is no way to configure this in the Growatt settings. You can play with the other modes but you won’t be able to achieve a set up like I’ve described.
OK he we go using python and goggle chrome you can take control of a website and automate your way through it: you will need to install the following:
pip install selenium
pip install paho-mqtt
pip install chromedriver-py
Now under the hood chrome gives all elements ( labels/textboxes/buttons…ect) in a web page a kind of address called a xpath. You need to use chrome’s development tool to inspect an element and get its xpath. Some elements have IDs or class names you can use instead of a raw path. The reason I’m explaining this is that on occasion Growatt changes the structure of the page and thus the xpath can change and you would need to update your code.
Below you will find a quick and dirty python program that will get you into the basics of what I’m doing. I’ve included a MQTT publish so you can see you can get any data you want back into home assistant so you can monitor current settings of your inverter. I run my code on mini pc running under systemd service on ubuntu but you can use windows as well (code works on both).
In my version I run the my code under a scheduler every 5 mins but you could just modify it so it lives in a while 1: loop and runs forever…
The line #options.add_argument(‘–headless’) is committed out just remove the # and the chrome window will remain hidden but when testing its useful to see what the script is doing to chrome but remember not to interact with the browser when it is running as you may introduce changes to the xpaths.
Hope this is useful
Weird
import sys
import os
import json
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager
import time
from datetime import datetime
import paho.mqtt.client as mqtt
import paho.mqtt.publish as publish
GROWATT_USERNAME = ‘your growatt web username here’
GROWATT_PASSWORD = ‘your growatt web password here’
MQTT_USERNAME = ‘mqtt username here’
MQTT_PASSWORD = ‘mqtt password here’
GROWATT_URL = ‘Login’
WINDOW_SIZE = “1920,1080”
USERNAME_FIELD_XPATH = ‘//[@id=“val_loginAccount”]’
PASSWORD_FIELD_XPATH = '//[@id=“val_loginPwd”]’
LOGIN_BUTTON_XPATH = ‘//[@class=“hasColorBtn loginB”]’
SOC_TEXT_XPATH = '//[@id=“panel_body”]/div/div/div[1]/div[1]/div[13]/span[2]’
CURRENT_MODE_TEXT_XPATH = ‘//[@id=“panel_body”]/div/div/div[1]/div[1]/div[2]/span’
SETTINGS_BUTTON_XPATH = '//[@class=“op-set radiuTop”]’
LOAD_FIRST_DISCHARGE_TEXT_XPATH = ‘//[@class=“allInput ipt-int wloadSOCLowLimit2”]’
SETTINGS_PASSWORD_TEXT_XPATH = '//[@id=“val_dialog_setSPA_pwd”]’
SETTINGS_SAVE_BUTTON_XPATH = ‘/html/body/div[12]/div/div[3]/div[1]/span[1]’
def login():
options = Options()
#options.add_argument(‘–headless’) #Uncomment this line to hide the chrome window
driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()), options=options)
time.sleep(2)
driver.maximize_window()
driver.get(GROWATT_URL)
time.sleep(2)
username_textbox = driver.find_element(‘xpath’,USERNAME_FIELD_XPATH)
username_textbox.send_keys(GROWATT_USERNAME)
password_textbox = driver.find_element(‘xpath’,PASSWORD_FIELD_XPATH)
password_textbox.send_keys(GROWATT_PASSWORD)
signin_button = driver.find_element(‘xpath’,LOGIN_BUTTON_XPATH)
signin_button.click()
return driver
def getCurrentSOC(driver):
value = driver.find_element(‘xpath’,SOC_TEXT_XPATH)
capacity = value.text
soc = capacity.replace(‘%’,‘’)
return soc
def getCurrentMode(driver):
value = driver.find_element(‘xpath’,CURRENT_MODE_TEXT_XPATH)
mode = value.text
return mode
def getLoadFirstStop(driver):
stop_textbox = driver.find_element(‘xpath’,LOAD_FIRST_DISCHARGE_TEXT_XPATH)
value = stop_textbox.get_property(“value”)
return value
def setLoadFirstStop(driver,STOP):
stop_textbox = driver.find_element(‘xpath’,LOAD_FIRST_DISCHARGE_TEXT_XPATH)
stop_textbox.clear()
stop_textbox.click()
stop_textbox.send_keys(STOP)
def openCurrentSettings(driver):
settings_button = driver.find_element(‘xpath’,SETTINGS_BUTTON_XPATH)
settings_button.click()
time.sleep(5)
def saveCurrentSettings(driver):
TODAY = datetime.today().strftime(‘%Y%m%d’)
key_textbox = driver.find_element(‘xpath’,SETTINGS_PASSWORD_TEXT_XPATH)
key_textbox.send_keys(TODAY)
update_button = driver.find_element(‘xpath’,SETTINGS_SAVE_BUTTON_XPATH)
update_button.click()
def main():
driver = login()
time.sleep(5) # Wait for Page To Load
currentSOC = getCurrentSOC(driver)
currentMODE = getCurrentMode(driver)
openCurrentSettings(driver)
time.sleep(5) # Wait for Page To Load
currentLoadFirstStop = getLoadFirstStop(driver)
''' WINTER SETING'''
DAYLOADSTOP = 75
EVENINGLOADSTOP = 10
NOW = datetime.now()
NOWTIME = NOW.strftime("%H:%M")
HOUR = int(NOW.strftime("%H"))
if HOUR >= 5 and HOUR < 16:
if not currentLoadFirstStop == DAYLOADSTOP:
print(f'INFO : {NOWTIME} Setting LOAD FIRST STOP = {DAYLOADSTOP}')
setLoadFirstStop(driver,DAYLOADSTOP)
saveCurrentSettings(driver)
time.sleep(5)# Wait for Page To Load
else:
if not currentLoadFirstStop == EVENINGLOADSTOP:
print(f'INFO : {NOWTIME} Setting LOAD FIRST STOP = {EVENINGLOADSTOP}')
setLoadFirstStop(driver,EVENINGLOADSTOP)
saveCurrentSettings(driver)
time.sleep(5)# Wait for Page To Load
driver.close()
driver.quit()
try:
payload = json.dumps({"currentsoc": int(currentSOC),"currentstop": int(currentLoadFirstStop),"currentmode": currentMODE,"runtime": NOWTIME})
publish.single("/battery/state",payload,hostname="homeassistant",auth={"username": MQTT_USERNAME, "password": MQTT_PASSWORD})
except:
print(f'ERROR : {NOWTIME} FAILED TO UPDATE HOME ASSISTANT')
if name == ‘main’:
sys.exit(main())