From d278dd94777e602f23dce3690158fe414042b2fe Mon Sep 17 00:00:00 2001 From: indykoning <15870933+indykoning@users.noreply.github.com> Date: Tue, 16 Jun 2020 21:16:29 +0200 Subject: [PATCH] Add growatt battery devices (#34773) Co-authored-by: Martin Hjelmare --- .../components/growatt_server/manifest.json | 2 +- .../components/growatt_server/sensor.py | 298 +++++++++++++++--- requirements_all.txt | 2 +- 3 files changed, 248 insertions(+), 54 deletions(-) diff --git a/homeassistant/components/growatt_server/manifest.json b/homeassistant/components/growatt_server/manifest.json index 7d8a8a3852f..775ada9076d 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.0.1"], + "requirements": ["growattServer==0.0.4"], "codeowners": ["@indykoning"] } diff --git a/homeassistant/components/growatt_server/sensor.py b/homeassistant/components/growatt_server/sensor.py index c228bcbe4ab..95d6ffd0ff7 100644 --- a/homeassistant/components/growatt_server/sensor.py +++ b/homeassistant/components/growatt_server/sensor.py @@ -29,81 +29,253 @@ DEFAULT_PLANT_ID = "0" DEFAULT_NAME = "Growatt" SCAN_INTERVAL = datetime.timedelta(minutes=5) +# Sensor type order is: Sensor name, Unit of measurement, api data name, additional options + TOTAL_SENSOR_TYPES = { - "total_money_today": ("Total money today", "€", "plantMoneyText", None), - "total_money_total": ("Money lifetime", "€", "totalMoneyText", None), - "total_energy_today": ( - "Energy Today", - ENERGY_KILO_WATT_HOUR, - "todayEnergy", - "power", + "total_money_today": ("Total money today", "€", "plantMoneyText", {}), + "total_money_total": ("Money lifetime", "€", "totalMoneyText", {}), + "total_energy_today": ("Energy Today", ENERGY_KILO_WATT_HOUR, "todayEnergy", {},), + "total_output_power": ( + "Output Power", + POWER_WATT, + "invTodayPpv", + {"device_class": "power"}, ), - "total_output_power": ("Output Power", POWER_WATT, "invTodayPpv", "power"), "total_energy_output": ( "Lifetime energy output", ENERGY_KILO_WATT_HOUR, "totalEnergy", - "power", + {}, + ), + "total_maximum_output": ( + "Maximum power", + POWER_WATT, + "nominalPower", + {"device_class": "power"}, ), - "total_maximum_output": ("Maximum power", POWER_WATT, "nominalPower", "power"), } INVERTER_SENSOR_TYPES = { - "inverter_energy_today": ( - "Energy today", - ENERGY_KILO_WATT_HOUR, - "e_today", - "power", - ), + "inverter_energy_today": ("Energy today", ENERGY_KILO_WATT_HOUR, "e_today", {},), "inverter_energy_total": ( "Lifetime energy output", ENERGY_KILO_WATT_HOUR, "e_total", - "power", + {}, ), - "inverter_voltage_input_1": ("Input 1 voltage", VOLT, "vpv1", None), + "inverter_voltage_input_1": ("Input 1 voltage", VOLT, "vpv1", {}), "inverter_amperage_input_1": ( "Input 1 Amperage", ELECTRICAL_CURRENT_AMPERE, "ipv1", - None, + {}, ), - "inverter_wattage_input_1": ("Input 1 Wattage", POWER_WATT, "ppv1", "power"), - "inverter_voltage_input_2": ("Input 2 voltage", VOLT, "vpv2", None), + "inverter_wattage_input_1": ( + "Input 1 Wattage", + POWER_WATT, + "ppv1", + {"device_class": "power"}, + ), + "inverter_voltage_input_2": ("Input 2 voltage", VOLT, "vpv2", {}), "inverter_amperage_input_2": ( "Input 2 Amperage", ELECTRICAL_CURRENT_AMPERE, "ipv2", - None, + {}, ), - "inverter_wattage_input_2": ("Input 2 Wattage", POWER_WATT, "ppv2", "power"), - "inverter_voltage_input_3": ("Input 3 voltage", VOLT, "vpv3", None), + "inverter_wattage_input_2": ( + "Input 2 Wattage", + POWER_WATT, + "ppv2", + {"device_class": "power"}, + ), + "inverter_voltage_input_3": ("Input 3 voltage", VOLT, "vpv3", {}), "inverter_amperage_input_3": ( "Input 3 Amperage", ELECTRICAL_CURRENT_AMPERE, "ipv3", - None, + {}, ), - "inverter_wattage_input_3": ("Input 3 Wattage", POWER_WATT, "ppv3", "power"), - "inverter_internal_wattage": ("Internal wattage", POWER_WATT, "ppv", "power"), - "inverter_reactive_voltage": ("Reactive voltage", VOLT, "vacr", None), + "inverter_wattage_input_3": ( + "Input 3 Wattage", + POWER_WATT, + "ppv3", + {"device_class": "power"}, + ), + "inverter_internal_wattage": ( + "Internal wattage", + POWER_WATT, + "ppv", + {"device_class": "power"}, + ), + "inverter_reactive_voltage": ("Reactive voltage", VOLT, "vacr", {}), "inverter_inverter_reactive_amperage": ( "Reactive amperage", ELECTRICAL_CURRENT_AMPERE, "iacr", - None, + {}, + ), + "inverter_frequency": ("AC frequency", FREQUENCY_HERTZ, "fac", {}), + "inverter_current_wattage": ( + "Output power", + POWER_WATT, + "pac", + {"device_class": "power"}, ), - "inverter_frequency": ("AC frequency", FREQUENCY_HERTZ, "fac", None), - "inverter_current_wattage": ("Output power", POWER_WATT, "pac", "power"), "inverter_current_reactive_wattage": ( "Reactive wattage", POWER_WATT, "pacr", - "power", + {"device_class": "power"}, ), } -SENSOR_TYPES = {**TOTAL_SENSOR_TYPES, **INVERTER_SENSOR_TYPES} +STORAGE_SENSOR_TYPES = { + "storage_storage_production_today": ( + "Storage production today", + ENERGY_KILO_WATT_HOUR, + "eBatDisChargeToday", + {}, + ), + "storage_storage_production_lifetime": ( + "Lifetime Storage production", + ENERGY_KILO_WATT_HOUR, + "eBatDisChargeTotal", + {}, + ), + "storage_grid_discharge_today": ( + "Grid discharged today", + ENERGY_KILO_WATT_HOUR, + "eacDisChargeToday", + {}, + ), + "storage_load_consumption_today": ( + "Load consumption today", + ENERGY_KILO_WATT_HOUR, + "eopDischrToday", + {}, + ), + "storage_load_consumption_lifetime": ( + "Lifetime load consumption", + ENERGY_KILO_WATT_HOUR, + "eopDischrTotal", + {}, + ), + "storage_grid_charged_today": ( + "Grid charged today", + ENERGY_KILO_WATT_HOUR, + "eacChargeToday", + {}, + ), + "storage_charge_storage_lifetime": ( + "Lifetime storaged charged", + ENERGY_KILO_WATT_HOUR, + "eChargeTotal", + {}, + ), + "storage_solar_production": ( + "Solar power production", + POWER_WATT, + "ppv", + {"device_class": "power"}, + ), + "storage_battery_percentage": ( + "Battery percentage", + "%", + "capacity", + {"device_class": "battery"}, + ), + "storage_power_flow": ( + "Storage charging/ discharging(-ve)", + POWER_WATT, + "pCharge", + {"device_class": "power"}, + ), + "storage_load_consumption_solar_storage": ( + "Load consumption(Solar + Storage)", + "VA", + "rateVA", + {}, + ), + "storage_charge_today": ( + "Charge today", + ENERGY_KILO_WATT_HOUR, + "eChargeToday", + {}, + ), + "storage_import_from_grid": ( + "Import from grid", + POWER_WATT, + "pAcInPut", + {"device_class": "power"}, + ), + "storage_import_from_grid_today": ( + "Import from grid today", + ENERGY_KILO_WATT_HOUR, + "eToUserToday", + {}, + ), + "storage_import_from_grid_total": ( + "Import from grid total", + ENERGY_KILO_WATT_HOUR, + "eToUserTotal", + {}, + ), + "storage_load_consumption": ( + "Load consumption", + POWER_WATT, + "outPutPower", + {"device_class": "power"}, + ), + "storage_grid_voltage": ("AC input voltage", VOLT, "vGrid", {"round": 2}), + "storage_pv_charging_voltage": ("PV charging voltage", VOLT, "vpv", {"round": 2}), + "storage_ac_input_frequency_out": ( + "AC input frequency", + FREQUENCY_HERTZ, + "freqOutPut", + {"round": 2}, + ), + "storage_output_voltage": ("Output voltage", VOLT, "outPutVolt", {"round": 2}), + "storage_ac_output_frequency": ( + "Ac output frequency", + FREQUENCY_HERTZ, + "freqGrid", + {"round": 2}, + ), + "storage_current_PV": ( + "Solar charge current", + ELECTRICAL_CURRENT_AMPERE, + "iAcCharge", + {"round": 2}, + ), + "storage_current_1": ( + "Solar current to storage", + ELECTRICAL_CURRENT_AMPERE, + "iChargePV1", + {"round": 2}, + ), + "storage_grid_amperage_input": ( + "Grid charge current", + ELECTRICAL_CURRENT_AMPERE, + "chgCurr", + {"round": 2}, + ), + "storage_grid_out_current": ( + "Grid out current", + ELECTRICAL_CURRENT_AMPERE, + "outPutCurrent", + {"round": 2}, + ), + "storage_battery_voltage": ("Battery voltage", VOLT, "vBat", {"round": 2}), + "storage_load_percentage": ( + "Load percentage", + "%", + "loadPercent", + {"device_class": "battery", "round": 2}, + ), +} + +SENSOR_TYPES = {**TOTAL_SENSOR_TYPES, **INVERTER_SENSOR_TYPES, **STORAGE_SENSOR_TYPES} PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { @@ -134,25 +306,34 @@ def setup_platform(hass, config, add_entities, discovery_info=None): 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) + # Get a list of devices for specified plant to add sensors for. + devices = api.device_list(plant_id) entities = [] - probe = GrowattData(api, username, password, plant_id, True) + probe = GrowattData(api, username, password, plant_id, "total") 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: + # Add sensors for each device in the specified plant. + for device in devices: + probe = GrowattData( + api, username, password, device["deviceSn"], device["deviceType"] + ) + sensors = [] + if device["deviceType"] == "inverter": + sensors = INVERTER_SENSOR_TYPES + elif device["deviceType"] == "storage": + probe.plant_id = plant_id + sensors = STORAGE_SENSOR_TYPES + + for sensor in sensors: entities.append( GrowattInverter( probe, - f"{inverter['deviceAilas']}", + f"{device['deviceAilas']}", sensor, - f"{inverter['deviceSn']}-{sensor}", + f"{device['deviceSn']}-{sensor}", ) ) @@ -188,12 +369,16 @@ class GrowattInverter(Entity): @property def state(self): """Return the state of the sensor.""" - return self.probe.get_data(SENSOR_TYPES[self.sensor][2]) + result = self.probe.get_data(SENSOR_TYPES[self.sensor][2]) + round_to = SENSOR_TYPES[self.sensor][3].get("round") + if round_to is not None: + result = round(result, round_to) + return result @property def device_class(self): """Return the device class of the sensor.""" - return SENSOR_TYPES[self.sensor][3] + return SENSOR_TYPES[self.sensor][3].get("device_class") @property def unit_of_measurement(self): @@ -208,12 +393,13 @@ class GrowattInverter(Entity): class GrowattData: """The class for handling data retrieval.""" - def __init__(self, api, username, password, inverter_id, is_total=False): + def __init__(self, api, username, password, device_id, growatt_type): """Initialize the probe.""" - self.is_total = is_total + self.growatt_type = growatt_type self.api = api - self.inverter_id = inverter_id + self.device_id = device_id + self.plant_id = None self.data = {} self.username = username self.password = password @@ -222,19 +408,27 @@ class GrowattData: def update(self): """Update probe data.""" self.api.login(self.username, self.password) - _LOGGER.debug("Updating data for %s", self.inverter_id) + _LOGGER.debug("Updating data for %s", self.device_id) try: - if self.is_total: - total_info = self.api.plant_info(self.inverter_id) + if self.growatt_type == "total": + total_info = self.api.plant_info(self.device_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) + elif self.growatt_type == "inverter": + inverter_info = self.api.inverter_detail(self.device_id) self.data = inverter_info["data"] + elif self.growatt_type == "storage": + storage_info_detail = self.api.storage_params(self.device_id)[ + "storageDetailBean" + ] + storage_energy_overview = self.api.storage_energy_overview( + self.plant_id, self.device_id + ) + self.data = {**storage_info_detail, **storage_energy_overview} except json.decoder.JSONDecodeError: _LOGGER.error("Unable to fetch data from Growatt server") diff --git a/requirements_all.txt b/requirements_all.txt index 2fcbfc8efcb..f3c81a6555e 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -687,7 +687,7 @@ greenwavereality==0.5.1 griddypower==0.1.0 # homeassistant.components.growatt_server -growattServer==0.0.1 +growattServer==0.0.4 # homeassistant.components.gstreamer gstreamer-player==1.1.2