From 0ca3186caf0b1c28d045a0ab413e1dcc254ec085 Mon Sep 17 00:00:00 2001 From: muppet3000 Date: Tue, 13 Apr 2021 14:40:30 +0100 Subject: [PATCH] Add 'mix' system support for Growatt integration (#49026) * Added 'mix' system support for Growatt integration * Changed Growatt "Last Data Update" to a timestamp * Changed Growatt "Last Data Update" to UTC * Accepted suggested change for Growatt "Last Data Update" Co-authored-by: Martin Hjelmare Co-authored-by: Martin Hjelmare --- CODEOWNERS | 2 +- .../components/growatt_server/manifest.json | 4 +- .../components/growatt_server/sensor.py | 251 +++++++++++++++++- requirements_all.txt | 2 +- 4 files changed, 251 insertions(+), 8 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index ff0372b0d1f..862df7b687a 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -183,7 +183,7 @@ homeassistant/components/gpsd/* @fabaff homeassistant/components/gree/* @cmroche homeassistant/components/greeneye_monitor/* @jkeljo homeassistant/components/group/* @home-assistant/core -homeassistant/components/growatt_server/* @indykoning +homeassistant/components/growatt_server/* @indykoning @muppet3000 homeassistant/components/guardian/* @bachya homeassistant/components/habitica/* @ASMfreaK @leikoilja homeassistant/components/harmony/* @ehendrix23 @bramkragten @bdraco @mkeesey diff --git a/homeassistant/components/growatt_server/manifest.json b/homeassistant/components/growatt_server/manifest.json index d60f91d191c..8da456aa76a 100644 --- a/homeassistant/components/growatt_server/manifest.json +++ b/homeassistant/components/growatt_server/manifest.json @@ -2,6 +2,6 @@ "domain": "growatt_server", "name": "Growatt", "documentation": "https://www.home-assistant.io/integrations/growatt_server/", - "requirements": ["growattServer==0.1.1"], - "codeowners": ["@indykoning"] + "requirements": ["growattServer==1.0.0"], + "codeowners": ["@indykoning", "@muppet3000"] } diff --git a/homeassistant/components/growatt_server/sensor.py b/homeassistant/components/growatt_server/sensor.py index 86b88872a8a..6464dee6729 100644 --- a/homeassistant/components/growatt_server/sensor.py +++ b/homeassistant/components/growatt_server/sensor.py @@ -18,27 +18,28 @@ from homeassistant.const import ( DEVICE_CLASS_ENERGY, DEVICE_CLASS_POWER, DEVICE_CLASS_TEMPERATURE, + DEVICE_CLASS_TIMESTAMP, DEVICE_CLASS_VOLTAGE, ELECTRICAL_CURRENT_AMPERE, ENERGY_KILO_WATT_HOUR, FREQUENCY_HERTZ, PERCENTAGE, + POWER_KILO_WATT, POWER_WATT, TEMP_CELSIUS, VOLT, ) import homeassistant.helpers.config_validation as cv -from homeassistant.util import Throttle +from homeassistant.util import Throttle, dt _LOGGER = logging.getLogger(__name__) CONF_PLANT_ID = "plant_id" DEFAULT_PLANT_ID = "0" DEFAULT_NAME = "Growatt" -SCAN_INTERVAL = datetime.timedelta(minutes=5) +SCAN_INTERVAL = datetime.timedelta(minutes=1) # Sensor type order is: Sensor name, Unit of measurement, api data name, additional options - TOTAL_SENSOR_TYPES = { "total_money_today": ("Total money today", CURRENCY_EURO, "plantMoneyText", {}), "total_money_total": ("Money lifetime", CURRENCY_EURO, "totalMoneyText", {}), @@ -345,7 +346,207 @@ STORAGE_SENSOR_TYPES = { ), } -SENSOR_TYPES = {**TOTAL_SENSOR_TYPES, **INVERTER_SENSOR_TYPES, **STORAGE_SENSOR_TYPES} +MIX_SENSOR_TYPES = { + # Values from 'mix_info' API call + "mix_statement_of_charge": ( + "Statement of charge", + PERCENTAGE, + "capacity", + {"device_class": DEVICE_CLASS_BATTERY}, + ), + "mix_battery_charge_today": ( + "Battery charged today", + ENERGY_KILO_WATT_HOUR, + "eBatChargeToday", + {"device_class": DEVICE_CLASS_ENERGY}, + ), + "mix_battery_charge_lifetime": ( + "Lifetime battery charged", + ENERGY_KILO_WATT_HOUR, + "eBatChargeTotal", + {"device_class": DEVICE_CLASS_ENERGY}, + ), + "mix_battery_discharge_today": ( + "Battery discharged today", + ENERGY_KILO_WATT_HOUR, + "eBatDisChargeToday", + {"device_class": DEVICE_CLASS_ENERGY}, + ), + "mix_battery_discharge_lifetime": ( + "Lifetime battery discharged", + ENERGY_KILO_WATT_HOUR, + "eBatDisChargeTotal", + {"device_class": DEVICE_CLASS_ENERGY}, + ), + "mix_solar_generation_today": ( + "Solar energy today", + ENERGY_KILO_WATT_HOUR, + "epvToday", + {"device_class": DEVICE_CLASS_ENERGY}, + ), + "mix_solar_generation_lifetime": ( + "Lifetime solar energy", + ENERGY_KILO_WATT_HOUR, + "epvTotal", + {"device_class": DEVICE_CLASS_ENERGY}, + ), + "mix_battery_discharge_w": ( + "Battery discharging W", + POWER_WATT, + "pDischarge1", + {"device_class": DEVICE_CLASS_POWER}, + ), + "mix_battery_voltage": ( + "Battery voltage", + VOLT, + "vbat", + {"device_class": DEVICE_CLASS_VOLTAGE}, + ), + "mix_pv1_voltage": ( + "PV1 voltage", + VOLT, + "vpv1", + {"device_class": DEVICE_CLASS_VOLTAGE}, + ), + "mix_pv2_voltage": ( + "PV2 voltage", + VOLT, + "vpv2", + {"device_class": DEVICE_CLASS_VOLTAGE}, + ), + # Values from 'mix_totals' API call + "mix_load_consumption_today": ( + "Load consumption today", + ENERGY_KILO_WATT_HOUR, + "elocalLoadToday", + {"device_class": DEVICE_CLASS_ENERGY}, + ), + "mix_load_consumption_lifetime": ( + "Lifetime load consumption", + ENERGY_KILO_WATT_HOUR, + "elocalLoadTotal", + {"device_class": DEVICE_CLASS_ENERGY}, + ), + "mix_export_to_grid_today": ( + "Export to grid today", + ENERGY_KILO_WATT_HOUR, + "etoGridToday", + {"device_class": DEVICE_CLASS_ENERGY}, + ), + "mix_export_to_grid_lifetime": ( + "Lifetime export to grid", + ENERGY_KILO_WATT_HOUR, + "etogridTotal", + {"device_class": DEVICE_CLASS_ENERGY}, + ), + # Values from 'mix_system_status' API call + "mix_battery_charge": ( + "Battery charging", + POWER_KILO_WATT, + "chargePower", + {"device_class": DEVICE_CLASS_POWER}, + ), + "mix_load_consumption": ( + "Load consumption", + POWER_KILO_WATT, + "pLocalLoad", + {"device_class": DEVICE_CLASS_POWER}, + ), + "mix_wattage_pv_1": ( + "PV1 Wattage", + POWER_WATT, + "pPv1", + {"device_class": DEVICE_CLASS_POWER}, + ), + "mix_wattage_pv_2": ( + "PV2 Wattage", + POWER_WATT, + "pPv2", + {"device_class": DEVICE_CLASS_POWER}, + ), + "mix_wattage_pv_all": ( + "All PV Wattage", + POWER_KILO_WATT, + "ppv", + {"device_class": DEVICE_CLASS_POWER}, + ), + "mix_export_to_grid": ( + "Export to grid", + POWER_KILO_WATT, + "pactogrid", + {"device_class": DEVICE_CLASS_POWER}, + ), + "mix_import_from_grid": ( + "Import from grid", + POWER_KILO_WATT, + "pactouser", + {"device_class": DEVICE_CLASS_POWER}, + ), + "mix_battery_discharge_kw": ( + "Battery discharging kW", + POWER_KILO_WATT, + "pdisCharge1", + {"device_class": DEVICE_CLASS_POWER}, + ), + "mix_grid_voltage": ( + "Grid voltage", + VOLT, + "vAc1", + {"device_class": DEVICE_CLASS_VOLTAGE}, + ), + # Values from 'mix_detail' API call + "mix_system_production_today": ( + "System production today (self-consumption + export)", + ENERGY_KILO_WATT_HOUR, + "eCharge", + {"device_class": DEVICE_CLASS_ENERGY}, + ), + "mix_load_consumption_solar_today": ( + "Load consumption today (solar)", + ENERGY_KILO_WATT_HOUR, + "eChargeToday", + {"device_class": DEVICE_CLASS_ENERGY}, + ), + "mix_self_consumption_today": ( + "Self consumption today (solar + battery)", + ENERGY_KILO_WATT_HOUR, + "eChargeToday1", + {"device_class": DEVICE_CLASS_ENERGY}, + ), + "mix_load_consumption_battery_today": ( + "Load consumption today (battery)", + ENERGY_KILO_WATT_HOUR, + "echarge1", + {"device_class": DEVICE_CLASS_ENERGY}, + ), + "mix_import_from_grid_today": ( + "Import from grid today (load)", + ENERGY_KILO_WATT_HOUR, + "etouser", + {"device_class": DEVICE_CLASS_ENERGY}, + ), + # This sensor is manually created using the most recent X-Axis value from the chartData + "mix_last_update": ( + "Last Data Update", + None, + "lastdataupdate", + {"device_class": DEVICE_CLASS_TIMESTAMP}, + ), + # Values from 'dashboard_data' API call + "mix_import_from_grid_today_combined": ( + "Import from grid today (load + charging)", + ENERGY_KILO_WATT_HOUR, + "etouser_combined", # This id is not present in the raw API data, it is added by the sensor + {"device_class": DEVICE_CLASS_ENERGY}, + ), +} + +SENSOR_TYPES = { + **TOTAL_SENSOR_TYPES, + **INVERTER_SENSOR_TYPES, + **STORAGE_SENSOR_TYPES, + **MIX_SENSOR_TYPES, +} PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { @@ -396,6 +597,9 @@ def setup_platform(hass, config, add_entities, discovery_info=None): elif device["deviceType"] == "storage": probe.plant_id = plant_id sensors = STORAGE_SENSOR_TYPES + elif device["deviceType"] == "mix": + probe.plant_id = plant_id + sensors = MIX_SENSOR_TYPES else: _LOGGER.debug( "Device type %s was found but is not supported right now", @@ -504,6 +708,45 @@ class GrowattData: self.plant_id, self.device_id ) self.data = {**storage_info_detail, **storage_energy_overview} + elif self.growatt_type == "mix": + mix_info = self.api.mix_info(self.device_id) + mix_totals = self.api.mix_totals(self.device_id, self.plant_id) + mix_system_status = self.api.mix_system_status( + self.device_id, self.plant_id + ) + + mix_detail = self.api.mix_detail( + self.device_id, self.plant_id, date=datetime.datetime.now() + ) + # Get the chart data and work out the time of the last entry, use this as the last time data was published to the Growatt Server + mix_chart_entries = mix_detail["chartData"] + sorted_keys = sorted(mix_chart_entries) + + # Create datetime from the latest entry + date_now = dt.now().date() + last_updated_time = dt.parse_time(str(sorted_keys[-1])) + combined_timestamp = datetime.datetime.combine( + date_now, last_updated_time + ) + # Convert datetime to UTC + combined_timestamp_utc = dt.as_utc(combined_timestamp) + mix_detail["lastdataupdate"] = combined_timestamp_utc.isoformat() + + # Dashboard data is largely inaccurate for mix system but it is the only call with the ability to return the combined + # imported from grid value that is the combination of charging AND load consumption + dashboard_data = self.api.dashboard_data(self.plant_id) + # Dashboard values have units e.g. "kWh" as part of their returned string, so we remove it + dashboard_values_for_mix = { + # etouser is already used by the results from 'mix_detail' so we rebrand it as 'etouser_combined' + "etouser_combined": dashboard_data["etouser"].replace("kWh", "") + } + self.data = { + **mix_info, + **mix_totals, + **mix_system_status, + **mix_detail, + **dashboard_values_for_mix, + } except json.decoder.JSONDecodeError: _LOGGER.error("Unable to fetch data from Growatt server") diff --git a/requirements_all.txt b/requirements_all.txt index 3d76cd9e1e6..66324962193 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -708,7 +708,7 @@ greeneye_monitor==2.1 greenwavereality==0.5.1 # homeassistant.components.growatt_server -growattServer==0.1.1 +growattServer==1.0.0 # homeassistant.components.gstreamer gstreamer-player==1.1.2