1 """Read status of growatt inverters."""
3 from __future__
import annotations
26 LOGIN_INVALID_AUTH_CODE,
28 from .inverter
import INVERTER_SENSOR_TYPES
29 from .mix
import MIX_SENSOR_TYPES
30 from .sensor_entity_description
import GrowattSensorEntityDescription
31 from .storage
import STORAGE_SENSOR_TYPES
32 from .tlx
import TLX_SENSOR_TYPES
33 from .total
import TOTAL_SENSOR_TYPES
35 _LOGGER = logging.getLogger(__name__)
37 SCAN_INTERVAL = datetime.timedelta(minutes=5)
41 """Retrieve the device list for the selected plant."""
42 plant_id = config[CONF_PLANT_ID]
45 login_response = api.login(config[CONF_USERNAME], config[CONF_PASSWORD])
47 not login_response[
"success"]
48 and login_response[
"msg"] == LOGIN_INVALID_AUTH_CODE
51 user_id = login_response[
"user"][
"id"]
52 if plant_id == DEFAULT_PLANT_ID:
53 plant_info = api.plant_list(user_id)
54 plant_id = plant_info[
"data"][0][
"plantId"]
57 devices = api.device_list(plant_id)
58 return [devices, plant_id]
63 config_entry: ConfigEntry,
64 async_add_entities: AddEntitiesCallback,
66 """Set up the Growatt sensor."""
67 config = {**config_entry.data}
68 username = config[CONF_USERNAME]
69 password = config[CONF_PASSWORD]
70 url = config.get(CONF_URL, DEFAULT_URL)
71 name = config[CONF_NAME]
74 if url
in DEPRECATED_URLS:
76 "URL: %s has been deprecated, migrating to the latest default: %s",
81 config[CONF_URL] = url
82 hass.config_entries.async_update_entry(config_entry, data=config)
85 api = growattServer.GrowattApi(add_random_user_id=
True, agent_identifier=username)
88 devices, plant_id = await hass.async_add_executor_job(get_device_list, api, config)
90 probe =
GrowattData(api, username, password, plant_id,
"total")
95 unique_id=f
"{plant_id}-{description.key}",
96 description=description,
98 for description
in TOTAL_SENSOR_TYPES
102 for device
in devices:
104 api, username, password, device[
"deviceSn"], device[
"deviceType"]
106 sensor_descriptions: tuple[GrowattSensorEntityDescription, ...] = ()
107 if device[
"deviceType"] ==
"inverter":
108 sensor_descriptions = INVERTER_SENSOR_TYPES
109 elif device[
"deviceType"] ==
"tlx":
110 probe.plant_id = plant_id
111 sensor_descriptions = TLX_SENSOR_TYPES
112 elif device[
"deviceType"] ==
"storage":
113 probe.plant_id = plant_id
114 sensor_descriptions = STORAGE_SENSOR_TYPES
115 elif device[
"deviceType"] ==
"mix":
116 probe.plant_id = plant_id
117 sensor_descriptions = MIX_SENSOR_TYPES
120 "Device type %s was found but is not supported right now",
121 device[
"deviceType"],
128 name=f
"{device['deviceAilas']}",
129 unique_id=f
"{device['deviceSn']}-{description.key}",
130 description=description,
132 for description
in sensor_descriptions
140 """Representation of a Growatt Sensor."""
142 _attr_has_entity_name =
True
144 entity_description: GrowattSensorEntityDescription
147 self, probe, name, unique_id, description: GrowattSensorEntityDescription
149 """Initialize a PVOutput sensor."""
157 identifiers={(DOMAIN, probe.device_id)},
158 manufacturer=
"Growatt",
164 """Return the state of the sensor."""
172 """Return the unit of measurement of the sensor, if any."""
174 return self.
probeprobe.get_currency()
175 return super().native_unit_of_measurement
178 """Get the latest data from the Growat API and updates the state."""
183 """The class for handling data retrieval."""
185 def __init__(self, api, username, password, device_id, growatt_type):
186 """Initialize the probe."""
197 @Throttle(SCAN_INTERVAL)
199 """Update probe data."""
204 total_info = self.
apiapi.plant_info(self.
device_iddevice_id)
205 del total_info[
"deviceList"]
207 plant_money_text, currency = total_info[
"plantMoneyText"].split(
"/")
208 total_info[
"plantMoneyText"] = plant_money_text
209 total_info[
"currency"] = currency
210 self.
datadata = total_info
212 inverter_info = self.
apiapi.inverter_detail(self.
device_iddevice_id)
213 self.
datadata = inverter_info
215 tlx_info = self.
apiapi.tlx_detail(self.
device_iddevice_id)
216 self.
datadata = tlx_info[
"data"]
218 storage_info_detail = self.
apiapi.storage_params(self.
device_iddevice_id)[
221 storage_energy_overview = self.
apiapi.storage_energy_overview(
224 self.
datadata = {**storage_info_detail, **storage_energy_overview}
226 mix_info = self.
apiapi.mix_info(self.
device_iddevice_id)
228 mix_system_status = self.
apiapi.mix_system_status(
235 mix_chart_entries = mix_detail[
"chartData"]
236 sorted_keys = sorted(mix_chart_entries)
239 date_now = dt_util.now().
date()
240 last_updated_time = dt_util.parse_time(
str(sorted_keys[-1]))
241 mix_detail[
"lastdataupdate"] = datetime.datetime.combine(
242 date_now, last_updated_time, dt_util.get_default_time_zone()
248 dashboard_data = self.
apiapi.dashboard_data(self.
plant_idplant_id)
251 dashboard_values_for_mix = {
254 "etouser_combined":
float(
255 dashboard_data[
"etouser"].replace(
"kWh",
"")
263 **dashboard_values_for_mix,
266 "Finished updating data for %s (%s)",
270 except json.decoder.JSONDecodeError:
271 _LOGGER.error(
"Unable to fetch data from Growatt server")
274 """Get the currency."""
275 return self.
datadata.
get(
"currency")
280 "Data request for: %s",
281 entity_description.name,
283 variable = entity_description.api_key
284 api_value = self.
datadata.
get(variable)
286 return_value = api_value
290 entity_description.previous_value_drop_threshold
is not None
291 and previous_value
is not None
292 and api_value
is not None
296 "%s - Drop threshold specified (%s), checking for drop... API"
297 " Value: %s, Previous Value: %s"
299 entity_description.name,
300 entity_description.previous_value_drop_threshold,
304 diff =
float(api_value) -
float(previous_value)
311 if -(entity_description.previous_value_drop_threshold) <= diff < 0:
314 "Diff is negative, but only by a small amount therefore not a"
315 " nightly reset, using previous value (%s) instead of api value"
321 return_value = previous_value
324 "%s - No drop detected, using API value", entity_description.name
340 if entity_description.never_resets
and api_value == 0
and previous_value:
343 "API value is 0, but this value should never reset, returning"
344 " previous value (%s) instead"
348 return_value = previous_value
def get_data(self, entity_description)
def __init__(self, api, username, password, device_id, growatt_type)
str|None native_unit_of_measurement(self)
None __init__(self, probe, name, unique_id, GrowattSensorEntityDescription description)
web.Response get(self, web.Request request, str config_key)
None async_setup_entry(HomeAssistant hass, ConfigEntry config_entry, AddEntitiesCallback async_add_entities)
def get_device_list(api, config)