1 """Support for GoodWe inverter via UDP."""
3 from __future__
import annotations
5 from collections.abc
import Callable
6 from dataclasses
import dataclass
7 from datetime
import date, datetime, timedelta
8 from decimal
import Decimal
10 from typing
import Any
12 from goodwe
import Inverter, Sensor, SensorKind
17 SensorEntityDescription,
25 UnitOfElectricCurrent,
26 UnitOfElectricPotential,
42 from .const
import DOMAIN, KEY_COORDINATOR, KEY_DEVICE_INFO, KEY_INVERTER
43 from .coordinator
import GoodweUpdateCoordinator
45 _LOGGER = logging.getLogger(__name__)
48 BATTERY_SOC =
"battery_soc"
55 DAILY_RESET = [
"e_day",
"e_load_day"]
67 "e_bat_discharge_total",
70 _ICONS: dict[SensorKind, str] = {
71 SensorKind.PV:
"mdi:solar-power",
72 SensorKind.AC:
"mdi:power-plug-outline",
73 SensorKind.UPS:
"mdi:power-plug-off-outline",
74 SensorKind.BAT:
"mdi:battery-high",
75 SensorKind.GRID:
"mdi:transmission-tower",
79 @dataclass(frozen=True)
81 """Class describing Goodwe sensor entities."""
83 value: Callable[[GoodweUpdateCoordinator, str], Any] = (
84 lambda coordinator, sensor: coordinator.sensor_value(sensor)
86 available: Callable[[GoodweUpdateCoordinator], bool] = (
87 lambda coordinator: coordinator.last_update_success
91 _DESCRIPTIONS: dict[str, GoodweSensorEntityDescription] = {
94 device_class=SensorDeviceClass.CURRENT,
95 state_class=SensorStateClass.MEASUREMENT,
96 native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
100 device_class=SensorDeviceClass.VOLTAGE,
101 state_class=SensorStateClass.MEASUREMENT,
102 native_unit_of_measurement=UnitOfElectricPotential.VOLT,
106 device_class=SensorDeviceClass.POWER,
107 state_class=SensorStateClass.MEASUREMENT,
108 native_unit_of_measurement=UnitOfPower.WATT,
112 device_class=SensorDeviceClass.ENERGY,
113 state_class=SensorStateClass.TOTAL_INCREASING,
114 native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
115 value=
lambda coordinator, sensor: coordinator.total_sensor_value(sensor),
116 available=
lambda coordinator: coordinator.data
is not None,
120 device_class=SensorDeviceClass.APPARENT_POWER,
121 state_class=SensorStateClass.MEASUREMENT,
122 native_unit_of_measurement=UnitOfApparentPower.VOLT_AMPERE,
123 entity_registry_enabled_default=
False,
127 device_class=SensorDeviceClass.REACTIVE_POWER,
128 state_class=SensorStateClass.MEASUREMENT,
129 native_unit_of_measurement=UnitOfReactivePower.VOLT_AMPERE_REACTIVE,
130 entity_registry_enabled_default=
False,
134 device_class=SensorDeviceClass.TEMPERATURE,
135 state_class=SensorStateClass.MEASUREMENT,
136 native_unit_of_measurement=UnitOfTemperature.CELSIUS,
140 device_class=SensorDeviceClass.FREQUENCY,
141 state_class=SensorStateClass.MEASUREMENT,
142 native_unit_of_measurement=UnitOfFrequency.HERTZ,
146 device_class=SensorDeviceClass.DURATION,
147 state_class=SensorStateClass.MEASUREMENT,
148 native_unit_of_measurement=UnitOfTime.HOURS,
149 entity_registry_enabled_default=
False,
153 state_class=SensorStateClass.MEASUREMENT,
154 native_unit_of_measurement=PERCENTAGE,
159 state_class=SensorStateClass.MEASUREMENT,
168 config_entry: ConfigEntry,
169 async_add_entities: AddEntitiesCallback,
171 """Set up the GoodWe inverter from a config entry."""
172 entities: list[InverterSensor] = []
173 inverter = hass.data[DOMAIN][config_entry.entry_id][KEY_INVERTER]
174 coordinator = hass.data[DOMAIN][config_entry.entry_id][KEY_COORDINATOR]
175 device_info = hass.data[DOMAIN][config_entry.entry_id][KEY_DEVICE_INFO]
180 for sensor
in inverter.sensors()
181 if not sensor.id_.startswith(
"xx")
188 """Entity representing individual inverter sensor."""
190 entity_description: GoodweSensorEntityDescription
194 coordinator: GoodweUpdateCoordinator,
195 device_info: DeviceInfo,
199 """Initialize an inverter sensor."""
202 self.
_attr_unique_id_attr_unique_id = f
"{DOMAIN}-{sensor.id_}-{inverter.serial_number}"
205 EntityCategory.DIAGNOSTIC
if sensor.id_
not in _MAIN_SENSORS
else None
210 if "Enum" in type(sensor).__name__
or sensor.id_ ==
"timestamp":
217 if sensor.id_ == BATTERY_SOC:
220 self.
_stop_reset_stop_reset: Callable[[],
None] |
None =
None
224 """Return the value reported by the sensor."""
229 """Return if entity is available.
231 We delegate the behavior to entity description lambda, since
232 some sensors (like energy produced today) should report themselves
233 as available even when the (non-battery) pv inverter is off-line during night
234 and most of the sensors are actually unavailable.
240 """Reset the value back to 0 at midnight.
242 Some sensors values like daily produced energy are kept available,
243 even when the inverter is in sleep mode and no longer responds to request.
244 In contrast to "total" sensors, these "daily" sensors need to be reset to 0 on midnight.
246 if not self.coordinator.last_update_success:
249 _LOGGER.debug(
"Goodwe reset %s to 0", self.
namenamename)
250 next_midnight = dt_util.start_of_local_day(
251 dt_util.now() +
timedelta(days=1, minutes=1)
258 """Schedule reset task at midnight."""
259 if self.
_sensor_sensor.id_
in DAILY_RESET:
260 next_midnight = dt_util.start_of_local_day(
269 """Remove reset task at midnight."""
270 if self.
_sensor_sensor.id_
in DAILY_RESET
and self.
_stop_reset_stop_reset
is not None:
None reset_sensor(self, str sensor)
def async_reset(self, now)
None async_added_to_hass(self)
_attr_native_unit_of_measurement
None __init__(self, GoodweUpdateCoordinator coordinator, DeviceInfo device_info, Inverter inverter, Sensor sensor)
StateType|date|datetime|Decimal native_value(self)
None async_will_remove_from_hass(self)
None async_write_ha_state(self)
str|UndefinedType|None name(self)
None async_setup_entry(HomeAssistant hass, ConfigEntry config_entry, AddEntitiesCallback async_add_entities)
CALLBACK_TYPE async_track_point_in_time(HomeAssistant hass, HassJob[[datetime], Coroutine[Any, Any, None]|None]|Callable[[datetime], Coroutine[Any, Any, None]|None] action, datetime point_in_time)