Add growatt server integration (#25635)

* Added growatt server integration

* Ran black formatter

* Processed feedback.

* Made attributes more readable.

* Create a sensor for each property.

* Added unique_ids

* Accidentally flipped functions

* Added dynamic device classes.

* Fixed stale session.
pull/26557/head
indykoning 2019-09-09 23:47:44 +02:00 committed by Martin Hjelmare
parent 051639b6ad
commit a4fd991ab5
6 changed files with 207 additions and 0 deletions

View File

@ -249,6 +249,7 @@ omit =
homeassistant/components/greeneye_monitor/sensor.py
homeassistant/components/greenwave/light.py
homeassistant/components/group/notify.py
homeassistant/components/growatt_server/sensor.py
homeassistant/components/gstreamer/media_player.py
homeassistant/components/gtfs/sensor.py
homeassistant/components/gtt/sensor.py

View File

@ -110,6 +110,7 @@ homeassistant/components/google_translate/* @awarecan
homeassistant/components/google_travel_time/* @robbiet480
homeassistant/components/gpsd/* @fabaff
homeassistant/components/group/* @home-assistant/core
homeassistant/components/growatt_server/* @indykoning
homeassistant/components/gtfs/* @robbiet480
homeassistant/components/harmony/* @ehendrix23
homeassistant/components/hassio/* @home-assistant/hass-io

View File

@ -0,0 +1 @@
"""The Growatt server PV inverter sensor integration."""

View File

@ -0,0 +1,12 @@
{
"domain": "growatt_server",
"name": "Growatt Server",
"documentation": "https://www.home-assistant.io/components/growatt_server/",
"requirements": [
"growattServer==0.0.1"
],
"dependencies": [],
"codeowners": [
"@indykoning"
]
}

View File

@ -0,0 +1,189 @@
"""Read status of growatt inverters."""
import re
import json
import logging
import datetime
import growattServer
import voluptuous as vol
from homeassistant.util import Throttle
from homeassistant.helpers.entity import Entity
import homeassistant.helpers.config_validation as cv
from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import CONF_NAME, CONF_USERNAME, CONF_PASSWORD
_LOGGER = logging.getLogger(__name__)
CONF_PLANT_ID = "plant_id"
DEFAULT_PLANT_ID = "0"
DEFAULT_NAME = "Growatt"
SCAN_INTERVAL = datetime.timedelta(minutes=5)
TOTAL_SENSOR_TYPES = {
"total_money_today": ("Total money today", "", "plantMoneyText", None),
"total_money_total": ("Money lifetime", "", "totalMoneyText", None),
"total_energy_today": ("Energy Today", "kWh", "todayEnergy", "power"),
"total_output_power": ("Output Power", "W", "invTodayPpv", "power"),
"total_energy_output": ("Lifetime energy output", "kWh", "totalEnergy", "power"),
"total_maximum_output": ("Maximum power", "W", "nominalPower", "power"),
}
INVERTER_SENSOR_TYPES = {
"inverter_energy_today": ("Energy today", "kWh", "e_today", "power"),
"inverter_energy_total": ("Lifetime energy output", "kWh", "e_total", "power"),
"inverter_voltage_input_1": ("Input 1 voltage", "V", "vpv1", None),
"inverter_amperage_input_1": ("Input 1 Amperage", "A", "ipv1", None),
"inverter_wattage_input_1": ("Input 1 Wattage", "W", "ppv1", "power"),
"inverter_voltage_input_2": ("Input 2 voltage", "V", "vpv2", None),
"inverter_amperage_input_2": ("Input 2 Amperage", "A", "ipv2", None),
"inverter_wattage_input_2": ("Input 2 Wattage", "W", "ppv2", "power"),
"inverter_voltage_input_3": ("Input 3 voltage", "V", "vpv3", None),
"inverter_amperage_input_3": ("Input 3 Amperage", "A", "ipv3", None),
"inverter_wattage_input_3": ("Input 3 Wattage", "W", "ppv3", "power"),
"inverter_internal_wattage": ("Internal wattage", "W", "ppv", "power"),
"inverter_reactive_voltage": ("Reactive voltage", "V", "vacr", None),
"inverter_inverter_reactive_amperage": ("Reactive amperage", "A", "iacr", None),
"inverter_frequency": ("AC frequency", "Hz", "fac", None),
"inverter_current_wattage": ("Output power", "W", "pac", "power"),
"inverter_current_reactive_wattage": ("Reactive wattage", "W", "pacr", "power"),
}
SENSOR_TYPES = {**TOTAL_SENSOR_TYPES, **INVERTER_SENSOR_TYPES}
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
{
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_PLANT_ID, default=DEFAULT_PLANT_ID): cv.string,
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
}
)
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Growatt sensor."""
username = config[CONF_USERNAME]
password = config[CONF_PASSWORD]
plant_id = config[CONF_PLANT_ID]
name = config[CONF_NAME]
api = growattServer.GrowattApi()
# Log in to api and fetch first plant if no plant id is defined.
login_response = api.login(username, password)
if not login_response["success"] and login_response["errCode"] == "102":
_LOGGER.error("Username or Password may be incorrect!")
return
user_id = login_response["userId"]
if plant_id == DEFAULT_PLANT_ID:
plant_info = api.plant_list(user_id)
plant_id = plant_info["data"][0]["plantId"]
# Get a list of inverters for specified plant to add sensors for.
inverters = api.inverter_list(plant_id)
entities = []
probe = GrowattData(api, username, password, plant_id, True)
for sensor in TOTAL_SENSOR_TYPES:
entities.append(
GrowattInverter(probe, f"{name} Total", sensor, f"{plant_id}-{sensor}")
)
# Add sensors for each inverter in the specified plant.
for inverter in inverters:
probe = GrowattData(api, username, password, inverter["deviceSn"], False)
for sensor in INVERTER_SENSOR_TYPES:
entities.append(
GrowattInverter(
probe,
f"{inverter['deviceAilas']}",
sensor,
f"{inverter['deviceSn']}-{sensor}",
)
)
add_entities(entities, True)
class GrowattInverter(Entity):
"""Representation of a Growatt Sensor."""
def __init__(self, probe, name, sensor, unique_id):
"""Initialize a PVOutput sensor."""
self.sensor = sensor
self.probe = probe
self._name = name
self._state = None
self._unique_id = unique_id
@property
def name(self):
"""Return the name of the sensor."""
return f"{self._name} {SENSOR_TYPES[self.sensor][0]}"
@property
def unique_id(self):
"""Return the unique id of the sensor."""
return self._unique_id
@property
def icon(self):
"""Return the icon of the sensor."""
return "mdi:solar-power"
@property
def state(self):
"""Return the state of the sensor."""
return self.probe.get_data(SENSOR_TYPES[self.sensor][2])
@property
def device_class(self):
"""Return the device class of the sensor."""
return SENSOR_TYPES[self.sensor][3]
@property
def unit_of_measurement(self):
"""Return the unit of measurement of this entity, if any."""
return SENSOR_TYPES[self.sensor][1]
def update(self):
"""Get the latest data from the Growat API and updates the state."""
self.probe.update()
class GrowattData:
"""The class for handling data retrieval."""
def __init__(self, api, username, password, inverter_id, is_total=False):
"""Initialize the probe."""
self.is_total = is_total
self.api = api
self.inverter_id = inverter_id
self.data = {}
self.username = username
self.password = password
@Throttle(SCAN_INTERVAL)
def update(self):
"""Update probe data."""
self.api.login(self.username, self.password)
_LOGGER.debug("Updating data for %s", self.inverter_id)
try:
if self.is_total:
total_info = self.api.plant_info(self.inverter_id)
del total_info["deviceList"]
# PlantMoneyText comes in as "3.1/€" remove anything that isn't part of the number
total_info["plantMoneyText"] = re.sub(
r"[^\d.,]", "", total_info["plantMoneyText"]
)
self.data = total_info
else:
inverter_info = self.api.inverter_detail(self.inverter_id)
self.data = inverter_info["data"]
except json.decoder.JSONDecodeError:
_LOGGER.error("Unable to fetch data from Growatt server")
def get_data(self, variable):
"""Get the data."""
return self.data.get(variable)

View File

@ -585,6 +585,9 @@ greeneye_monitor==1.0
# homeassistant.components.greenwave
greenwavereality==0.5.1
# homeassistant.components.growatt_server
growattServer==0.0.1
# homeassistant.components.gstreamer
gstreamer-player==1.1.2