Anyone experience with connecting a Growatt solar-inverter?

I own a Growatt solar-inverter. This is connected to a server of the factory in China to get my power-stats to an app on my iPhone. I was wondering if anyone has integrated this to HA?

there’s an API manual: http://www.growatt.pl/dokumenty/Inne/Growatt%20Server%20Open%20API%20protocol%20standards.pdf

I have hacked together a custom sensor for my Growatt Inverter using the client library from https://github.com/Sjord/growatt_api_client. This is an adaptation of PVOutput Sensor.
The sensor outputs Current energy output in kW and you can get the Day’s Total Output using a template sensor.
Copy the python file growatt.py to custom_components/sensor folder

"""
Support for Growatt Invertor using API Library from https://github.com/Sjord/growatt_api_client thanks to Sjoerd Langkemper(https://github.com/Sjord)
Copyright 2018 Anil Roy and Sjoerd Langkemper
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""
import logging
import voluptuous as vol
from collections import namedtuple
from enum import IntEnum
import datetime
import hashlib
import json
import requests
from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import (
    ATTR_TEMPERATURE, CONF_API_KEY, CONF_NAME, ATTR_DATE, ATTR_TIME,
    ATTR_VOLTAGE)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity


"""Start the logger"""
_LOGGER = logging.getLogger(__name__)

ATTR_POWER_GENERATION = 'power_generation'

"""configuration for accessing the Unifi Controller"""
CONF_USERNAME = 'username'
CONF_PASSWORD = 'password'
DEFAULT_UNIT = 'kW'
DEFAULT_NAME = 'Growatt'
SCAN_INTERVAL = datetime.timedelta(minutes=5)

"""Define the schema for the Sensor Platform"""
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
    vol.Required(CONF_USERNAME): cv.string,
    vol.Required(CONF_PASSWORD): cv.string,
})


def setup_platform(hass, config, add_devices, discovery_info=None):
    """Set up the Growatt sensor."""
    """get all the parameters passed by the user to access the controller"""
    username = config.get(CONF_USERNAME)
    password = config.get(CONF_PASSWORD)
    api = GrowattApi()
    login_res = api.login(username, password)
    user_id = login_res['userId']
    plant_info = api.plant_list(user_id)
    try:
        ctrl = api.plant_list(user_id)
    except APIError as ex:
        _LOGGER.error("Failed to connect to Growatt server: %s", ex)
        return False
    # the controller was loaded properly now get the user groups to find the one you want
    add_devices([GrowatttSensor(api, 'Growatt', username, password)], True)

class GrowatttSensor(Entity):
    """Representation of a Growattt Sensor."""

    def __init__(self, api, name, u, p):
        """Initialize a PVOutput sensor."""
        self.api = api
        self._name = name
        self.username = u
        self.password = p
        self._state = None
        self._unit_of_measurement = 'kW'
        self.totalPowerToday = '0'
        self.status = namedtuple(
            'status', [ATTR_DATE, ATTR_TIME,
                       ATTR_POWER_GENERATION,
                       ATTR_TEMPERATURE, ATTR_VOLTAGE])

    @property
    def name(self):
        """Return the name of the sensor."""
        return self._name

    @property
    def state(self):
        """Return the state of the sensor."""
        return self._state
    
    @property
    def unit_of_measurement(self):
        """Return the unit of measurement of this entity, if any."""
        return self._unit_of_measurement
    

    @property
    def device_state_attributes(self):
        """Return the state attributes of the monitored installation."""
        return {
            ATTR_POWER_GENERATION: self.totalPowerToday,
        }


    def update(self):
        """Get the latest data from the Growat API and updates the state."""
        try:
            login_res = self.api.login(self.username, self.password)
            user_id = login_res['userId']
            plant_info = self.api.plant_list(user_id)
            data, kw = (plant_info['data'][0]['currentPower']).split( )
            self._state = data
          #  _LOGGER.info(
          #      "Growatt data is. {}".format(kw))
            if kw == 'W':
              data1 = float(data)/1000
             # _LOGGER.error("Growatt data is devided by 1000 {}".format(kw))
              self._state = data1
            #self._state = data
            self.totalPowerToday, kwh = (plant_info['data'][0]['todayEnergy']).split( )
            #self.totalPowerToday = 
        except TypeError:
            _LOGGER.error(
                "Unable to fetch data from Growatt server. %s")
        
        
'''
Copied directly from https://github.com/Sjord/growatt_api_client thanks to Sjoerd Langkemper(https://github.com/Sjord)
'''
def hash_password(password):
    """
    Normal MD5, except add c if a byte of the digest is less than 10.
    """
    password_md5 = hashlib.md5(password.encode('utf-8')).hexdigest()
    for i in range(0, len(password_md5), 2):
        if password_md5[i] == '0':
            password_md5 = password_md5[0:i] + 'c' + password_md5[i + 1:]
    return password_md5


class Timespan(IntEnum):
    day = 1
    month = 2


class GrowattApi:
    server_url = 'http://server.growatt.com/'

    def __init__(self):
        self.session = requests.Session()

    def get_url(self, page):
        return self.server_url + page

    def login(self, username, password):
        password_md5 = hash_password(password)
        response = self.session.post(self.get_url('LoginAPI.do'), data={
            'userName': username,
            'password': password_md5
        })
        data = json.loads(response.content.decode('utf-8'))
        return data['back']

    def plant_list(self, user_id):
        response = self.session.get(self.get_url('PlantListAPI.do'),
                                    params={'userId': user_id},
                                    allow_redirects=False)
        if response.status_code != 200:
            raise RuntimeError("Request failed: %s", response)
        data = json.loads(response.content.decode('utf-8'))
        return data['back']

    def plant_detail(self, plant_id, timespan, date):
        assert timespan in Timespan
        if timespan == Timespan.day:
            date_str = date.strftime('%Y-%m-%d')
        elif timespan == Timespan.month:
            date_str = date.strftime('%Y-%m')

        response = self.session.get(self.get_url('PlantDetailAPI.do'), params={
            'plantId': plant_id,
            'type': timespan.value,
            'date': date_str
        })
        data = json.loads(response.content.decode('utf-8'))
        return data['back']

#Copied part ends)

in configuration.yaml add

sensor:
  - platform: growatt
    username: 'username_for_growatt_server'
    password: 'pwd_for_growatt_server'

  - platform: template
  sensors:
      energy_generation:
        value_template: '{% if is_state_attr("sensor.growatt", "power_generation", "NaN") %}0{% else %}{{ "%0.2f"|format(states.sensor.growatt.attributes.power_generation|float) }}{% endif %}'
        friendly_name: 'Generated'
        unit_of_measurement: 'kWh'

This is my first try in to doing a sensor, my python may be terrible.
7/08/18 Fixed bug

Thanks 4 the info. I will try this when I’m back from my summerholiday!

getting this back

Sun Mar 17 2019 10:28:39 GMT+0100 (Midden-Europese standaardtijd)

Growatt data is. W

That’s more than i got :wink: I don’t seem to get te custom_component thing working…

I did see on github that there is a integrated component coming, I guess in this wensday

That would be super!

If you are that far you must have the sensor working have you added it to the Lovelace card?
I was using

 _LOGGER.error(
                "Growatt data is. {}".format(kw))

as sort of print command, should have used

 _LOGGER.info(
                "Growatt data is. {}".format(kw))
``` instead

V0.90 is online, no Growatt component…would have been great.

Point 4.2.5 in the manual.
Address:http://domain/v1/plant/data

Are you able to get a json output and paste it here. May be able to do what I did with my fronius inverter using json.

@anilet the syntax for custom_components is changed, I have now put the code above in the file ./custom_components/growatt/sensor.py

@Paul_Flavel Finally got my Growatt sensor working as a custom-component:

I also installed this on my Hassbian-Pi: API client by Sjord

When I start this python script with:

python -m growatt username password

I get a return with lot more “values” from the Growatt-server/API. Since I’m not a programmer, could you please show me how to add all these in the above python-script to get them to show up in HomeAssistant?

{‘totalData’: {‘isHaveStorage’: ‘false’, ‘CO2Sum’: ‘368.69 T’, ‘totalEnergySum’: ‘369.8 kWh’, ‘currentPowerSum’: ‘1.25 kW’, ‘eTotalMoneyText’: ‘78.2 (€)’, ‘todayEnergySum’: ‘14 kWh’}, ‘success’: True, ‘data’: [{‘plantName’: ‘4936 - Duhen’, ‘isHaveStorage’: ‘false’, ‘plantMoneyText’: ‘78.2 (€)’, ‘currentPower’: ‘1.25 kW’, ‘todayEnergy’: ‘14 kWh’, ‘plantId’: ‘71635’, ‘totalEnergy’: ‘369.8 kWh’}]}

{‘success’: True, ‘plantData’: {‘plantName’: ‘4936 - Duhen’, ‘plantMoneyText’: ‘3.0 (€)’, ‘plantId’: ‘71635’, ‘currentEnergy’: ‘14 kWh’}, ‘data’: {‘12:30’: ‘2182.05’, ‘13:30’: ‘1987.75’, ‘05:30’: ‘0’, ‘06:30’: ‘6.97’, ‘03:00’: ‘0’, ‘01:00’: ‘0’, ‘07:30’: ‘398.25’, ‘06:00’: ‘0’, ‘04:00’: ‘0’, ‘13:00’: ‘2128.67’, ‘12:00’: ‘2201.13’, ‘02:00’: ‘0’, ‘11:00’: ‘2136.43’, ‘09:00’: ‘1446.27’, ‘02:30’: ‘0’, ‘08:00’: ‘813.77’, ‘11:30’: ‘2188.67’, ‘14:30’: ‘1655.96’, ‘10:00’: ‘1919.92’, ‘04:30’: ‘0’, ‘10:30’: ‘2063.47’, ‘15:00’: ‘1457.63’, ‘00:30’: ‘0’, ‘01:30’: ‘0’, ‘09:30’: ‘1697.6’, ‘05:00’: ‘0’, ‘03:30’: ‘0’, ‘14:00’: ‘1831.33’, ‘08:30’: ‘1162.35’, ‘07:00’: ‘102.72’}}

Happy that you got the sensor working.
Most of these are historical data like total energy produced, total co2 reduced etc which are no interest to me or are easily calculated from HA>Influxdb>Grafana. The second part is the hourly data for charting on their site which we can get from HA anyway.

@anilet I think HA 0.92 broke my Growatt custom-component. Any ideas on how to get it going again?

  1. Move and rename growatt.py file to custom_components/growatt/sensor.py

  2. Create an empty init.py file in custom_components/growatt/

  3. Create manifest.json file in custom_components/growatt/ with

{
 "domain": "growatt",
 "name": "Growatt Sensor",
 "documentation": "",
 "dependencies": [],
 "codeowners": [],
 "requirements": []
} 

Thanks! Will try when I get home tonight

Works like a charm again, thanks a lot!

Also getting the same error, how did you get past this?

just follow the steps in anilet’s last post, that should do the trick

If you are stuck at the above error you can safely ignore it. That was a leftover from my testing,I have updated the code above to remove it.
If you are stuck at the new custom component format error follow instructions here
I have uploaded the custom_component to Github here

1 Like

Hi,
If I have 2 plants in 1 account, how do I create 2 sensors and monitor them separately? Please help me