# Growatt Binding
This binding supports the integration of Growatt solar inverters.
It depends on the independent Grott (opens new window) proxy server application. This intercepts the logging data that the Growatt inverter data logger normally sends directly to the Growatt cloud server. It sends the original (encoded) data onwards to the cloud server (so the cloud server will not notice anything different). But it also sends a (decoded) copy to openHAB as well.
# Supported Things
The binding supports two types of things:
bridge
: The bridge is the interface to the Grott application; it receives the data from all inverters.inverter
: The inverter thing contains channels which are updated with solor production and consumption data.
# Discovery
There is no automatic discovery of the bridge. However if a bridge exists and it receives inverter data, then a matching inverter thing is created in the Inbox.
# Thing Configuration
The bridge
thing allows configuration of the user credentials, which are only required if you want to send inverter commands via the Growatt cloud server:
Name | Type | Description | Advanced | Required |
---|---|---|---|---|
userName | text | User name for the Growatt Shine app. Only needed if using Rule Actions | yes | no |
password | text | Password for the Growatt Shine app. Only needed if using Rule Actions | yes | no |
The inverter
thing requires configuration of its serial number resp. deviceId
:
Name | Type | Description | Required |
---|---|---|---|
deviceId | text | Device serial number or id as configured in the Growatt cloud and the Grott application. | yes |
# Channels
The bridge
thing has no channels.
The inverter
thing supports many possible channels relating to solar generation and consumption.
All channels are read-only.
Depending on the inverter model, and its configuration, not all of the channels will be present.
The list of all possible channels is as follows:
Channel | Type | Description | Advanced |
---|---|---|---|
system-status | Number:Dimensionless | Inverter status code. | |
pv1-voltage | Number:ElectricPotential | DC voltage from solar panel string #1. | yes |
pv2-voltage | Number:ElectricPotential | DC voltage from solar panel string #2. | yes |
pv1-current | Number:ElectricCurrent | DC current from solar panel string #1. | yes |
pv2-current | Number:ElectricCurrent | DC current from solar panel string #2. | yes |
pv-power | Number:Power | Total DC solar input power. | |
pv1-power | Number:Power | DC power from solar panel string #1. | yes |
pv2-power | Number:Power | DC power from solar panel string #2. | yes |
grid-frequency | Number:Frequency | Frequency of the grid. | yes |
grid-voltage-r | Number:ElectricPotential | Voltage of the grid (phase #R). | |
grid-voltage-s | Number:ElectricPotential | Voltage of the grid phase #S. | yes |
grid-voltage-t | Number:ElectricPotential | Voltage of the grid phase #T. | yes |
grid-voltage-rs | Number:ElectricPotential | Voltage of the grid phases #RS. | yes |
grid-voltage-st | Number:ElectricPotential | Voltage of the grid phases #ST. | yes |
grid-voltage-tr | Number:ElectricPotential | Voltage of the grid phases #TR. | yes |
inverter-current-r | Number:ElectricCurrent | AC current from inverter (phase #R). | yes |
inverter-current-s | Number:ElectricCurrent | AC current from inverter phase #S. | yes |
inverter-current-t | Number:ElectricCurrent | AC current from inverter phase #T. | yes |
inverter-power | Number:Power | Total AC output power from inverter. | |
inverter-power-r | Number:Power | AC power from inverter (phase #R). | |
inverter-power-s | Number:Power | AC power from inverter phase #S. | yes |
inverter-power-t | Number:Power | AC power from inverter phase #T. | yes |
inverter-va | Number:Power | AC VA from inverter. | yes |
export-power | Number:Power | Power exported to grid. | |
export-power-r | Number:Power | Power exported to grid phase #R. | yes |
export-power-s | Number:Power | Power exported to grid phase #S. | yes |
export-power-t | Number:Power | Power exported to grid phase #T. | yes |
import-power | Number:Power | Power imported from grid. | |
import-power-r | Number:Power | Power imported from grid phase #R. | yes |
import-power-s | Number:Power | Power imported from grid phase #S. | yes |
import-power-t | Number:Power | Power imported from grid phase #T. | yes |
load-power | Number:Power | Power supplied to load. | |
load-power-r | Number:Power | Power supplied to load phase #R. | yes |
load-power-s | Number:Power | Power supplied to load phase #S. | yes |
load-power-t | Number:Power | Power supplied to load phase #T. | yes |
charge-power | Number:Power | Battery charge power. | |
charge-current | Number:ElectricCurrent | Battery charge current. | yes |
discharge-power | Number:Power | Battery discharge power. | |
discharge-va | Number:Power | Battery discharge VA. | yes |
pv-energy-today | Number:Energy | DC energy collected by solar panels today. | |
pv1-energy-today | Number:Energy | DC energy collected by solar panels string #1 today. | yes |
pv2-energy-today | Number:Energy | DC energy collected by solar panels string #2 today. | yes |
pv-energy-total | Number:Energy | Total DC energy collected by solar panels. | |
pv1-energy-total | Number:Energy | Total DC energy collected by solar panels string #1. | yes |
pv2-energy-total | Number:Energy | Total DC energy collected by solar panels string #2. | yes |
inverter-energy-today | Number:Energy | AC energy produced by inverter today. | |
inverter-energy-total | Number:Energy | Total AC energy produced by inverter. | |
export-energy-today | Number:Energy | Energy exported today. | |
export-energy-total | Number:Energy | Total energy exported. | |
import-energy-today | Number:Energy | Energy imported today. | |
import-energy-total | Number:Energy | Total energy imported. | |
load-energy-today | Number:Energy | Energy supplied to load today. | |
load-energy-total | Number:Energy | Total energy supplied to load. | |
import-charge-energy-today | Number:Energy | Energy imported to charge battery today. | |
import-charge-energy-total | Number:Energy | Total energy imported to charge battery. | |
inverter-charge-energy-today | Number:Energy | Inverter energy to charge battery today. | |
inverter-charge-energy-total | Number:Energy | Total inverter energy to charge battery. | |
discharge-energy-today | Number:Energy | Energy consumed from battery. | |
discharge-energy-total | Number:Energy | Total energy consumed from battery. | |
total-work-time | Number:Time | Total work time of the system. | yes |
p-bus-voltage | Number:ElectricPotential | P Bus voltage. | yes |
n-bus-voltage | Number:ElectricPotential | N Bus voltage. | yes |
sp-bus-voltage | Number:ElectricPotential | SP Bus voltage. | yes |
pv-temperature | Number:Temperature | Temperature of the solar panels (string #1). | yes |
pv-ipm-temperature | Number:Temperature | Temperature of the IPM. | yes |
pv-boost-temperature | Number:Temperature | Boost temperature. | yes |
temperature-4 | Number:Temperature | Temperature #4. | yes |
pv2-temperature | Number:Temperature | Temperature of the solar panels (string #2). | yes |
battery-type | Number:Dimensionless | Type code of the battery. | yes |
battery-temperature | Number:Temperature | Battery temperature. | yes |
battery-voltage | Number:ElectricPotential | Battery voltage. | yes |
battery-display | Number:Dimensionless | Battery display code. | yes |
battery-soc | Number:Dimensionless | Battery State of Charge percent. | yes |
system-fault-0 | Number:Dimensionless | System fault code #0. | yes |
system-fault-1 | Number:Dimensionless | System fault code #1. | yes |
system-fault-2 | Number:Dimensionless | System fault code #2. | yes |
system-fault-3 | Number:Dimensionless | System fault code #3. | yes |
system-fault-4 | Number:Dimensionless | System fault code #4. | yes |
system-fault-5 | Number:Dimensionless | System fault code #5. | yes |
system-fault-6 | Number:Dimensionless | System fault code #6. | yes |
system-fault-7 | Number:Dimensionless | System fault code #7. | yes |
system-work-mode | Number:Dimensionless | System work mode code. | yes |
sp-display-status | Number:Dimensionless | Solar panel display status code. | yes |
constant-power-ok | Number:Dimensionless | Constant power OK code. | yes |
load-percent | Number:Dimensionless | Percent of full load. | yes |
rac | Number:Power | Reactive 'power' (var). | yes |
erac-today | Number:Energy | Reactive 'energy' today (kvarh). | yes |
erac-total | Number:Energy | Total reactive 'energy' (kvarh). | yes |
# Rule Actions
This binding includes rule actions, which allow you to setup programs for battery charging and discharging. Each inverter thing has a separate actions instance, which can be retrieved as follows.
val growattActions = getActions("growatt", "growatt:inverter:home:sph")
Where the first parameter must always be growatt
and the second must be the full inverter thing UID.
Once the action instance has been retrieved, you can invoke the following method:
growattActions.setupBatteryProgram(int programMode, @Nullable Integer powerLevel, @Nullable Integer stopSOC, @Nullable Boolean enableAcCharging, @Nullable String startTime, @Nullable String stopTime, @Nullable Boolean enableProgram)
The meaning of the method parameters is as follows:
Parameter | Description |
---|---|
programMode | The program mode to set i.e. 'Load First' (0), 'Battery First' (1), 'Grid First' (2). |
powerLevel2) | The percentage rate of battery (dis-)charge e.g. 100 - in 'Battery First' mode => charge power, otherwise => discharge power. |
stopSOC2) | The battery SOC (state of charge) percentage when the program shall stop e.g. 20 - in 'Battery First' mode => max. SOC, otherwise => min. SOC. |
enableAcCharging2) | Allow the battery to be charged from the AC mains supply e.g. true, false. |
startTime1,2) | String representation of the local time when the program time segment shall start e.g. "00:15" |
stopTime1,2) | String representation of the local time when the program time segment shall stop e.g. "06:45" |
enableProgram1,2) | Enable / disable the program time segment e.g. true, false |
Notes:
-1) WARNING inverters have different program time segment
's for each programMode
.
To prevent unexpected results do not overlap the time segment
's.
-2) Depending on inverter type and programMode
certain parameters may accept 'null' values.
The 'mix', 'sph' and 'spa' types set the battery program in a single command, so all parameters - except enableAcCharging
- must be non- 'null'.
By contrast 'tlx' types set the battery program in up to four partial commands, and you may pass 'null' parameters in order to omit a partial command.
The permission for passing 'null' parameters, and the effect of such 'null' parameters, is shown in detail in the table below:
Parameter | Permission for.. / effect of.. passing a 'null' parameter |
---|---|
programMode | Shall not be 'null' under any circumstance! |
powerLevel | May be 'null' on 'tlx' inverters whereby the prior programMode / powerLevel continues to apply. |
stopSOC | May be 'null' on 'tlx' inverters whereby the prior programMode / stopSOC continues to apply. |
enableAcCharging | If 'null' the prior enableAcCharging (if any) continues to apply. Shall not be 'null' on 'mix' inverter 'Battery First' program. |
startTime, stopTime, enableProgram | May be 'null' on 'tlx' inverters whereby the prior programMode / time segment continues to apply - note all 'null' resp. non-'null'. |
# Example program to charge battery during night-time low tariff time window
The following is an example program to charge the battery during a night-time low tariff period, and depending on the forecast solar energy for the coming day.
// solar power constants
val Integer programMode = 1 // 0 = Load First, 1 = Battery First, 2 = Grid First
val Integer powerLevel = 23 // percent
val batteryFull = 6500.0 // Wh
val batteryMin = 500.0 // Wh
val daylightConsumption = 10000.0 // Wh
val offPeakConsumption = 2000.0 // Wh
val maximumSOC = 100.0 // percent
val minimumSOC = 10.0 // percent
val maxChargingPower = 4000.0 // W
val offPeakEndMinute = 420 // 07:00
val offPeakStartMinute = 20 // 00:20
..
rule "Setup Solar Battery Charging Program"
when
Time cron "0 10 0 ? * * *"
then
val growattActions = getActions("growatt", "growatt:inverter:home:ABCD1234") // thing UID
if (growattActions === null) {
logWarn("Rules", "growattActions is null")
return
}
// variable algorithm parameters
var Integer startMinute = offPeakStartMinute
var Boolean enableProgram = true
var Boolean enableAcCharging = true
// calculate required stop SOC based on weather forecast
val Double solarForecast = (Solar_Forecast_Energy_Today_Full.state as QuantityType<Energy>).toUnit("Wh").doubleValue()
var Double targetSOC = (100.0 * (batteryMin + daylightConsumption - solarForecast)) / batteryFull
if (targetSOC > maximumSOC) {
targetSOC = maximumSOC
}
// calculate notional SOC at end of off peak period
var Double morningSOC = ((Battery_SOC_Level.state as QuantityType<Dimensionless>).toUnit("one").doubleValue() - (offPeakConsumption / batteryFull)) * 100.0
if (morningSOC < minimumSOC) {
morningSOC = minimumSOC
}
// calculate charging start time (if any)
if (targetSOC > morningSOC) {
startMinute = (offPeakEndMinute - (60.0 * (targetSOC - morningSOC) * batteryFull / (powerLevel * maxChargingPower))).intValue()
if (startMinute < offPeakStartMinute) {
startMinute = offPeakStartMinute
}
} else {
enableProgram = false
enableAcCharging = false
targetSOC = minimumSOC
}
// convert times to strings
val String startTime = String.format("%02d:%02d", startMinute / 60, startMinute % 60);
val String stopTime = String.format("%02d:%02d", offPeakEndMinute / 60, offPeakEndMinute % 60);
// convert to integer percent
val Integer stopSOC = targetSOC.intValue()
logInfo("Rules", "Setup Charging Program(morningSOC=" + morningSOC + "%, solarForecast=" + solarForecast + "Wh, programMode=" + programMode + ", powerLevel=" + powerLevel +
"%, stopSOC=" + stopSOC + "%, enableCharging=" + enableAcCharging + ", startTime=" + startTime + ", stopTime=" + stopTime + ", enableProgram=" + enableProgram +")")
growattActions.setupBatteryProgram(programMode, powerLevel, stopSOC, enableAcCharging, startTime, stopTime, enableProgram)
end
# Example program to charge battery prior to an extra high tariff window in the day
The following is an example program to charge the battery in preparation to avoid importing energy during a coming extra high tariff time window.
// solar power constants
var pauseProgramLastSetupDate
..
rule "Setup Solar Power Pause Program"
when
Time cron "59 0 8-22 ? * * *" or
Item Power_Pause_Program_Start changed
then
val programSetupDate = now.toLocalDate()
if (programSetupDate.equals(pauseProgramLastSetupDate)) {
logInfo("Rules", "Power Pause program already setup for " + programSetupDate)
return
}
val pauseStartState = Power_Pause_Program_Start.state
if (pauseStartState == NULL || pauseStartState == UNDEF) {
logWarn("Rules", "Power_Pause_Program_Start state is null or undefined")
return
}
var pauseStartDateTime = (pauseStartState as DateTimeType).getZonedDateTime()
if (pauseStartDateTime.getHour() < 8) {
logWarn("Rules", "Power Pause program shall not start before 08:00h => " + pauseStartDateTime)
return
}
val programDuration = Duration.between(now, pauseStartDateTime)
if (programDuration.isNegative() || programDuration.toDays() > 0) {
logInfo("Rules", "Power Pause program date is not today => " + pauseStartDateTime)
return
}
if (programDuration.toHours() < 1) {
logWarn("Rules", "Power Pause program lead time is too short => " + pauseStartDateTime)
return
}
// setup program to start late and end early in case inverter clock not in synch
val delta = 600
pauseStartDateTime = pauseStartDateTime.minusSeconds(delta)
val chargeStartDateTime = pauseStartDateTime.minusSeconds(programDuration.toSeconds()).plusSeconds(2 * delta)
if (chargeStartDateTime.isBefore(now)) {
logWarn("Rules", "Power Pause program start time is in the past")
return
}
val formatter = DateTimeFormatter.ofPattern("HH:mm");
val String stopTime = pauseStartDateTime.format(formatter)
val String startTime = chargeStartDateTime.format(formatter)
val socState = Battery_SOC_Level.state
if (socState == NULL || socState == UNDEF) {
logWarn("Rules", "Battery_SOC_Level is null or undefined")
return
}
val currentSOC = (socState as Number)
var targetPowerLevel = ((maximumSOC - currentSOC) * batteryFull) / (maxChargingPower * (programDurationSeconds / 3600.0))
if (targetPowerLevel < 23.0) {
targetPowerLevel = 23.0
} else if (targetPowerLevel > 100.0) {
targetPowerLevel = 100.0
}
val Integer powerLevel = targetPowerLevel.intValue()
val Integer programMode = 1 // 0 = Load First, 1 = Battery First, 2 = Grid First
val Boolean enableAcCharging = true
val Boolean enableProgram = true
val Integer stopSOC = maximumSOC.intValue()
val growattActions = getActions("growatt", "growatt:inverter:home:ABCD1234") // thing UID
if (growattActions === null) {
logWarn("Rules", "growattActions is null")
return
}
pauseProgramLastSetupDate = programSetupDate
logInfo("Rules", "Setup Solar Power Pause Program(programMode=" + programMode + ", powerLevel=" + powerLevel + "%, stopSOC=" + stopSOC + "%, enableCharging=" +
enableAcCharging + ", startTime=" + startTime + ", stopTime=" + stopTime + ", enableProgram=" + enableProgram +")")
growattActions.setupBatteryProgram(programMode, powerLevel, stopSOC, enableAcCharging, startTime, stopTime, enableProgram)
end
# Full Example
# Example .things
file
Bridge growatt:bridge:home "Growattt Bridge" [userName="USERNAME", password="PASSWORD"] {
Thing inverter sph "Growatt SPH Inverter" [deviceId="INVERTERTID"]
}
# Example .items
file
Number:ElectricPotential Solar_String1_Voltage "Solar String #1 PV Voltage" {channel="growatt:inverter:home:sph:pv1-voltage"}
Number:ElectricCurrent Solar_String1_Current "Solar String #1 PV Current" {channel="growatt:inverter:home:sph:pv1-current"}
Number:Power Solar_String1_Power "Solar String #1 PV Power" {channel="growatt:inverter:home:sph:pv1-power"}
Number:Energy Solar_Output_Energy "Solar Output Energy Total" {channel="growatt:inverter:home:sph:pv-energy-total"}
Example using a transform profile to invert an item value:
// charge item with positive value
Number:Power Charge_Power "Charge Power [%.0f W]" <energy> {channel="growatt:inverter:home:sph:charge-power"}
// discarge item with negative value
Number:Power Discharge_Power "Discharge Power [%.0f W]" <energy> {channel="growatt:inverter:home:sph:discharge-power" [ profile="transform:JS", toItemScript="| Quantity(input).multiply(-1).toString();" ] }
# Grott Application Installation and Setup
You can install the Grott application either on the same computer as openHAB or on another. The following assumes you will be running it on the same computer. The Grott application acts as a proxy server between your Growatt inverter and the Growatt cloud server. It intercepts data packets between the inverter and the cloud server, and it sends a copy of the intercepted data also to openHAB.
NOTE: make sure that the Grott application is FULLY OPERATIONAL for your inverter BEFORE you create any things in openHAB! Otherwise the binding might create a wrong (or even empty) list of channels for the inverter thing. (Yet if you do make that mistake you can rectify it by deleting and recreating the thing).
You should configure the Grott application via its grott.ini
file.
Configure Grott to match your inverter according to the instructions (opens new window).
# Install Python
If Python is not already installed on you computer, then install it first. And install the following additional necessary python packages:
sudo pip3 install paho-mqtt
sudo pip3 install requests
# Install Grott
First install the Grott application and the Grott application extension files in a Grott specific home folder.
Note that Grott requires the grottext.py
application extension in addition to the standard application files.
The installation is as follows:
- Create a 'home' sub-folder for Grott e.g.
/home/<username>/grott/
. - Copy
grott.py
,grottconf.py
,grottdata.py
,grottproxy.py
,grottsniffer.py
,grottserver.py
to the home folder. - Copy
grottext.py
application extension to the home folder. - Copy
grott.ini
configuration file to the home folder. - Modify
grott.ini
to run in proxy mode; not in compatibility mode; show your inverter type; not run MQTT; not run PVOutput; enable thegrottext
extension; and set the openHAB/growatt
servlet url.
A suggested Grott configuration for openHAB is as follows:
[Generic]
mode = proxy
compat = False
invtype = sph // your inverter type
[MQTT]
nomqtt = True // disable mqtt
[PVOutput]
pvoutput = False // disable pvoutput
[extension] // enable the 'grottext' extension
extension = True
extname = grottext
extvar = {"url": "http://127.0.0.1:8080/growatt"} // or ip address of openHAB (if remote)
# Start Grott as a Service
Finally you should set your computer to starts the Grott application automatically as a service when your computer starts. For Windows see wiki: https://github.com/johanmeijer/grott/wiki/Grott-as-a-service-(Windows) (opens new window) For Linux see wiki: https://github.com/johanmeijer/grott/wiki/Grott-as-a-service-(Linux) (opens new window) The service configuration for Linux is summarised below:
- Copy the
grott.service
file to the/etc/systemd/system/
folder - Modify
grott.service
to enter your user name; the Grott settings; the path to Python; and the path to the Grott application:
[Service]
SyslogIdentifier=grott
User=<username> // your username
WorkingDirectory=/home/<username>/grott/ // your home grott folder
ExecStart=-/usr/bin/python3 -u /home/<username>/grott/grott.py -v // ditto
And finally enable the Grott service:
sudo systemctl enable grott
# Route Growatt Inverter Logging via Grott Proxy
Normally the Growatt inverter sends its logging data directly to port 5279
on the Growatt server at server.growatt.com
(ip=47.91.67.66) on the cloud.
Grott is a proxy server that interposes itself beween the inverter and the cloud server.
i.e. it receives the inverter logging data and forwards it unchanged to the cloud server.
WARNING: make sure that Grott is running on a computer with a STATIC IP ADDRESS (and note this safely)! Otherwise if the computer changes its ip address dynamically, it can no longer intercept the inverter data. This means YOU WILL NO LONGER BE ABLE TO RESET THE INVERTER to its original settings!
You need to use the Growatt App to tell the inverter to send its logging data to the Grott proxy instead of to the cloud. See wiki: https://github.com/johanmeijer/grott/wiki/Rerouting-Growatt-Wifi-TCPIP-data-via-your-Grott-Server (opens new window) for more information.
← Groupe PSA Guntamatic →