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 <marhje52@gmail.com>

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
This commit is contained in:
muppet3000 2021-04-13 14:40:30 +01:00 committed by GitHub
parent 916ba0be11
commit 0ca3186caf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 251 additions and 8 deletions

View File

@ -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

View File

@ -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"]
}

View File

@ -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")

View File

@ -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