1 """Support for powerwall sensors."""
3 from __future__
import annotations
5 from collections.abc
import Callable
6 from dataclasses
import dataclass
7 from operator
import attrgetter, methodcaller
8 from typing
import TYPE_CHECKING, Generic, TypeVar
10 from tesla_powerwall
import GridState, MeterResponse, MeterType
15 SensorEntityDescription,
21 UnitOfElectricCurrent,
22 UnitOfElectricPotential,
31 from .const
import POWERWALL_COORDINATOR
32 from .entity
import BatteryEntity, PowerWallEntity
33 from .models
import BatteryResponse, PowerwallConfigEntry, PowerwallRuntimeData
35 _METER_DIRECTION_EXPORT =
"export"
36 _METER_DIRECTION_IMPORT =
"import"
38 _ValueParamT = TypeVar(
"_ValueParamT")
39 _ValueT = TypeVar(
"_ValueT", bound=float | int | str |
None)
42 @dataclass(frozen=True, kw_only=True)
44 SensorEntityDescription,
45 Generic[_ValueParamT, _ValueT],
47 """Describes Powerwall entity."""
49 value_fn: Callable[[_ValueParamT], _ValueT]
53 """Get the current value in kW."""
54 return meter.get_power(precision=3)
58 """Get the current value in Hz."""
59 return round(meter.frequency, 1)
63 """Get the current value in V."""
64 return round(meter.instant_average_voltage, 1)
67 POWERWALL_INSTANT_SENSORS = (
68 PowerwallSensorEntityDescription[MeterResponse, float](
70 translation_key=
"instant_power",
71 state_class=SensorStateClass.MEASUREMENT,
72 device_class=SensorDeviceClass.POWER,
73 native_unit_of_measurement=UnitOfPower.KILO_WATT,
74 value_fn=_get_meter_power,
76 PowerwallSensorEntityDescription[MeterResponse, float](
77 key=
"instant_frequency",
78 translation_key=
"instant_frequency",
79 state_class=SensorStateClass.MEASUREMENT,
80 device_class=SensorDeviceClass.FREQUENCY,
81 native_unit_of_measurement=UnitOfFrequency.HERTZ,
82 entity_registry_enabled_default=
False,
83 value_fn=_get_meter_frequency,
85 PowerwallSensorEntityDescription[MeterResponse, float](
86 key=
"instant_current",
87 translation_key=
"instant_current",
88 state_class=SensorStateClass.MEASUREMENT,
89 device_class=SensorDeviceClass.CURRENT,
90 native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
91 entity_registry_enabled_default=
False,
92 value_fn=methodcaller(
"get_instant_total_current"),
94 PowerwallSensorEntityDescription[MeterResponse, float](
95 key=
"instant_voltage",
96 translation_key=
"instant_voltage",
97 state_class=SensorStateClass.MEASUREMENT,
98 device_class=SensorDeviceClass.VOLTAGE,
99 native_unit_of_measurement=UnitOfElectricPotential.VOLT,
100 entity_registry_enabled_default=
False,
101 value_fn=_get_meter_average_voltage,
107 """Get the current value in V."""
108 return None if battery.v_out
is None else round(battery.v_out, 1)
112 """Get the current value in Hz."""
113 return None if battery.f_out
is None else round(battery.f_out, 1)
117 """Get the current value in A."""
118 return None if battery.i_out
is None else round(battery.i_out, 1)
121 BATTERY_INSTANT_SENSORS: list[PowerwallSensorEntityDescription] = [
122 PowerwallSensorEntityDescription[BatteryResponse, int](
123 key=
"battery_capacity",
124 translation_key=
"battery_capacity",
125 entity_category=EntityCategory.DIAGNOSTIC,
126 state_class=SensorStateClass.MEASUREMENT,
127 device_class=SensorDeviceClass.ENERGY_STORAGE,
128 native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
129 suggested_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
130 suggested_display_precision=1,
131 value_fn=attrgetter(
"capacity"),
133 PowerwallSensorEntityDescription[BatteryResponse, float |
None](
134 key=
"battery_instant_voltage",
135 translation_key=
"battery_instant_voltage",
136 entity_category=EntityCategory.DIAGNOSTIC,
137 state_class=SensorStateClass.MEASUREMENT,
138 device_class=SensorDeviceClass.VOLTAGE,
139 native_unit_of_measurement=UnitOfElectricPotential.VOLT,
140 value_fn=_get_instant_voltage,
142 PowerwallSensorEntityDescription[BatteryResponse, float |
None](
143 key=
"instant_frequency",
144 translation_key=
"instant_frequency",
145 entity_category=EntityCategory.DIAGNOSTIC,
146 state_class=SensorStateClass.MEASUREMENT,
147 device_class=SensorDeviceClass.FREQUENCY,
148 native_unit_of_measurement=UnitOfFrequency.HERTZ,
149 entity_registry_enabled_default=
False,
150 value_fn=_get_instant_frequency,
152 PowerwallSensorEntityDescription[BatteryResponse, float |
None](
153 key=
"instant_current",
154 translation_key=
"instant_current",
155 entity_category=EntityCategory.DIAGNOSTIC,
156 state_class=SensorStateClass.MEASUREMENT,
157 device_class=SensorDeviceClass.CURRENT,
158 native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
159 entity_registry_enabled_default=
False,
160 value_fn=_get_instant_current,
162 PowerwallSensorEntityDescription[BatteryResponse, int |
None](
164 translation_key=
"instant_power",
165 entity_category=EntityCategory.DIAGNOSTIC,
166 state_class=SensorStateClass.MEASUREMENT,
167 device_class=SensorDeviceClass.POWER,
168 native_unit_of_measurement=UnitOfPower.WATT,
169 value_fn=attrgetter(
"p_out"),
171 PowerwallSensorEntityDescription[BatteryResponse, float |
None](
172 key=
"battery_export",
173 translation_key=
"battery_export",
174 entity_category=EntityCategory.DIAGNOSTIC,
175 state_class=SensorStateClass.TOTAL_INCREASING,
176 device_class=SensorDeviceClass.ENERGY,
177 native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
178 suggested_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
179 suggested_display_precision=0,
180 value_fn=attrgetter(
"energy_discharged"),
182 PowerwallSensorEntityDescription[BatteryResponse, float |
None](
183 key=
"battery_import",
184 translation_key=
"battery_import",
185 entity_category=EntityCategory.DIAGNOSTIC,
186 state_class=SensorStateClass.TOTAL_INCREASING,
187 device_class=SensorDeviceClass.ENERGY,
188 native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
189 suggested_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
190 suggested_display_precision=0,
191 value_fn=attrgetter(
"energy_charged"),
193 PowerwallSensorEntityDescription[BatteryResponse, int](
194 key=
"battery_remaining",
195 translation_key=
"battery_remaining",
196 entity_category=EntityCategory.DIAGNOSTIC,
197 state_class=SensorStateClass.MEASUREMENT,
198 device_class=SensorDeviceClass.ENERGY_STORAGE,
199 native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
200 suggested_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
201 suggested_display_precision=1,
202 value_fn=attrgetter(
"energy_remaining"),
204 PowerwallSensorEntityDescription[BatteryResponse, str](
206 translation_key=
"grid_state",
207 entity_category=EntityCategory.DIAGNOSTIC,
208 device_class=SensorDeviceClass.ENUM,
209 options=[state.value.lower()
for state
in GridState],
210 value_fn=
lambda battery_data: battery_data.grid_state.value.lower(),
217 entry: PowerwallConfigEntry,
218 async_add_entities: AddEntitiesCallback,
220 """Set up the powerwall sensors."""
221 powerwall_data = entry.runtime_data
222 coordinator = powerwall_data[POWERWALL_COORDINATOR]
223 assert coordinator
is not None
224 data = coordinator.data
225 entities: list[Entity] = [
229 if data.backup_reserve
is not None:
232 for meter
in data.meters.meters:
237 for description
in POWERWALL_INSTANT_SENSORS
240 for battery
in data.batteries.values():
243 for description
in BATTERY_INSTANT_SENSORS
250 """Representation of an Powerwall charge sensor."""
252 _attr_translation_key =
"charge"
253 _attr_state_class = SensorStateClass.MEASUREMENT
254 _attr_native_unit_of_measurement = PERCENTAGE
255 _attr_device_class = SensorDeviceClass.BATTERY
259 """Device Uniqueid."""
260 return f
"{self.base_unique_id}_charge"
264 """Get the current value in percentage."""
265 return round(self.
datadatadata.charge)
269 """Representation of an Powerwall Energy sensor."""
271 entity_description: PowerwallSensorEntityDescription[MeterResponse, float]
275 powerwall_data: PowerwallRuntimeData,
277 description: PowerwallSensorEntityDescription[MeterResponse, float],
279 """Initialize the sensor."""
284 self.
_attr_unique_id_attr_unique_id = f
"{self.base_unique_id}_{meter.value}_{description.key}"
288 """Get the current value."""
290 if meter
is not None:
297 """Representation of the Powerwall backup reserve setting."""
299 _attr_translation_key =
"backup_reserve"
300 _attr_state_class = SensorStateClass.MEASUREMENT
301 _attr_native_unit_of_measurement = PERCENTAGE
302 _attr_device_class = SensorDeviceClass.BATTERY
306 """Device Uniqueid."""
307 return f
"{self.base_unique_id}_backup_reserve"
311 """Get the current value in percentage."""
312 if self.
datadatadata.backup_reserve
is None:
314 return round(self.
datadatadata.backup_reserve)
318 """Representation of an Powerwall Direction Energy sensor."""
320 _attr_state_class = SensorStateClass.TOTAL
321 _attr_native_unit_of_measurement = UnitOfEnergy.KILO_WATT_HOUR
322 _attr_device_class = SensorDeviceClass.ENERGY
326 powerwall_data: PowerwallRuntimeData,
328 meter_direction: str,
330 """Initialize the sensor."""
334 self.
_attr_unique_id_attr_unique_id = f
"{self.base_unique_id}_{meter.value}_{meter_direction}"
338 """Check if the reading is actually available.
340 The device reports 0 when something goes wrong which
341 we do not want to include in statistics and its a
342 transient data error.
344 return super().available
and self.
metermeter
is not None
347 def meter(self) -> MeterResponse | None:
348 """Get the meter for the sensor."""
353 """Representation of an Powerwall Export sensor."""
357 powerwall_data: PowerwallRuntimeData,
360 """Initialize the sensor."""
361 super().
__init__(powerwall_data, meter, _METER_DIRECTION_EXPORT)
365 """Get the current value in kWh."""
366 meter = self.
metermeter
368 assert meter
is not None
369 return meter.get_energy_exported()
373 """Representation of an Powerwall Import sensor."""
377 powerwall_data: PowerwallRuntimeData,
380 """Initialize the sensor."""
381 super().
__init__(powerwall_data, meter, _METER_DIRECTION_IMPORT)
385 """Get the current value in kWh."""
386 meter = self.
metermeter
388 assert meter
is not None
389 return meter.get_energy_imported()
393 """Representation of an Powerwall Battery sensor."""
395 entity_description: PowerwallSensorEntityDescription[BatteryResponse, _ValueT]
399 powerwall_data: PowerwallRuntimeData,
400 battery: BatteryResponse,
401 description: PowerwallSensorEntityDescription[BatteryResponse, _ValueT],
403 """Initialize the sensor."""
405 super().
__init__(powerwall_data, battery)
411 """Get the current value."""
BatteryResponse battery_data(self)
int|None native_value(self)
float|int|str|None native_value(self)
None __init__(self, PowerwallRuntimeData powerwall_data, BatteryResponse battery, PowerwallSensorEntityDescription[BatteryResponse, _ValueT] description)
None __init__(self, PowerwallRuntimeData powerwall_data, MeterType meter, str meter_direction)
MeterResponse|None meter(self)
float|None native_value(self)
None __init__(self, PowerwallRuntimeData powerwall_data, MeterType meter, PowerwallSensorEntityDescription[MeterResponse, float] description)
float|None native_value(self)
None __init__(self, PowerwallRuntimeData powerwall_data, MeterType meter)
float|None native_value(self)
None __init__(self, PowerwallRuntimeData powerwall_data, MeterType meter)
float _get_meter_average_voltage(MeterResponse meter)
float|None _get_instant_frequency(BatteryResponse battery)
float _get_meter_power(MeterResponse meter)
float|None _get_instant_voltage(BatteryResponse battery)
float _get_meter_frequency(MeterResponse meter)
float|None _get_instant_current(BatteryResponse battery)
None async_setup_entry(HomeAssistant hass, PowerwallConfigEntry entry, AddEntitiesCallback async_add_entities)