From c579c8dd8068d7aa7f3452af2ae254bcbd5f6536 Mon Sep 17 00:00:00 2001 From: darkrain-nl Date: Tue, 29 Sep 2026 18:21:19 +0200 Subject: [PATCH] Replace getattr lookups in Sofar with typed accessors (#183295) --- homeassistant/components/sofar/__init__.py | 7 +- .../components/sofar/binary_sensor.py | 16 +- homeassistant/components/sofar/coordinator.py | 3 +- homeassistant/components/sofar/select.py | 6 +- homeassistant/components/sofar/sensor.py | 244 +++++++++++++++++- homeassistant/components/sofar/switch.py | 5 +- tests/components/sofar/test_sensor.py | 55 ++++ 7 files changed, 303 insertions(+), 33 deletions(-) diff --git a/homeassistant/components/sofar/__init__.py b/homeassistant/components/sofar/__init__.py index bee12d35db4a..4c83c7fec3aa 100644 --- a/homeassistant/components/sofar/__init__.py +++ b/homeassistant/components/sofar/__init__.py @@ -12,7 +12,6 @@ from homeassistant.components.modbus import async_get_unit from homeassistant.components.sensor import ( DOMAIN as SENSOR_DOMAIN, SensorExtraStoredData, - SensorStateClass, ) from homeassistant.config_entries import ConfigEntryState from homeassistant.const import CONF_HOST, CONF_PORT, Platform @@ -104,7 +103,7 @@ def _async_seed_high_water_marks( registry = er.async_get(hass) last_states = restore_state.async_get(hass).last_states for description in SENSOR_DESCRIPTIONS: - if description.state_class is not SensorStateClass.TOTAL_INCREASING: + if (total_fn := description.total_fn) is None: continue entity_id = registry.async_get_entity_id( SENSOR_DOMAIN, DOMAIN, f"{serial}_{description.key}" @@ -116,9 +115,7 @@ def _async_seed_high_water_marks( extra = SensorExtraStoredData.from_dict(stored.extra_data.as_dict()) if extra is None or not isinstance(extra.native_value, (int, float)): continue - getattr(device, description.component).seed_high_water( - description.key, float(extra.native_value) - ) + total_fn(device).seed(float(extra.native_value)) async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: diff --git a/homeassistant/components/sofar/binary_sensor.py b/homeassistant/components/sofar/binary_sensor.py index 8c7d9ad1883b..817bf4407c73 100644 --- a/homeassistant/components/sofar/binary_sensor.py +++ b/homeassistant/components/sofar/binary_sensor.py @@ -1,9 +1,11 @@ """Support for Sofar binary sensors.""" +from collections.abc import Callable from dataclasses import dataclass from enum import IntFlag from typing import override +from sofar_modbus.modern.device import SofarInverter from sofar_modbus.modern.enums import PowerControlFlags from sofar_modbus.modern.faults import FaultCategory @@ -60,7 +62,7 @@ class SofarFlagBinarySensorDescription( ): """Describe a Sofar binary sensor backed by one flags-register bit.""" - attribute: str + flags_fn: Callable[[SofarInverter], IntFlag | None] flag: IntFlag @@ -71,7 +73,7 @@ FLAG_SENSOR_DESCRIPTIONS: tuple[SofarFlagBinarySensorDescription, ...] = ( translation_key="active_power_limit_enabled", entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, - attribute="power_control", + flags_fn=lambda device: device.active_power_control.power_control, flag=PowerControlFlags.ACTIVE_POWER, ), ) @@ -105,10 +107,9 @@ class SofarFaultBinarySensor(SofarEntity, BinarySensorEntity): @property @override def is_on(self) -> bool: - component = getattr(self.coordinator.device, self.entity_description.component) return any( fault.category is self.entity_description.category - for fault in component.active_faults + for fault in self.coordinator.device.state.active_faults ) @@ -119,7 +120,6 @@ class SofarFlagBinarySensor(SofarEntity, BinarySensorEntity): @property @override - def is_on(self) -> bool: - component = getattr(self.coordinator.device, self.entity_description.component) - flags = getattr(component, self.entity_description.attribute) - return self.entity_description.flag in flags + def is_on(self) -> bool | None: + flags = self.entity_description.flags_fn(self.coordinator.device) + return None if flags is None else self.entity_description.flag in flags diff --git a/homeassistant/components/sofar/coordinator.py b/homeassistant/components/sofar/coordinator.py index abcfe56a8644..c8e4aa6c2ebc 100644 --- a/homeassistant/components/sofar/coordinator.py +++ b/homeassistant/components/sofar/coordinator.py @@ -179,8 +179,7 @@ class SofarRuntimeData: component_name = BATTERY_COMPONENTS[number] if component_name not in self.served_components: return False - component = getattr(self.readings.device, component_name) - return bool(getattr(component, f"battery_voltage_{number}", None)) + return bool(self.readings.device.battery_string(number).voltage) def coordinator_for(self, component: str) -> SofarDataUpdateCoordinator: """Which coordinator owns a given component's data.""" diff --git a/homeassistant/components/sofar/select.py b/homeassistant/components/sofar/select.py index 88c27bc48d22..9f50b6abb932 100644 --- a/homeassistant/components/sofar/select.py +++ b/homeassistant/components/sofar/select.py @@ -28,6 +28,7 @@ class SofarSelectEntityDescription(SelectEntityDescription, SofarEntityDescripti """Describe a Sofar select entity.""" options_enum: type[IntEnum] + value_fn: Callable[[SofarInverter], IntEnum | None] write_fn: Callable[[SofarInverter, int], Awaitable[None]] @@ -38,6 +39,7 @@ SELECT_DESCRIPTIONS: tuple[SofarSelectEntityDescription, ...] = ( translation_key="charger_use_mode", options=_enum_options(ChargerUseMode), options_enum=ChargerUseMode, + value_fn=lambda device: device.charger.charger_use_mode, write_fn=lambda device, value: device.charger.write("charger_use_mode", value), ), SofarSelectEntityDescription( @@ -46,6 +48,7 @@ SELECT_DESCRIPTIONS: tuple[SofarSelectEntityDescription, ...] = ( translation_key="eps_control", options=_enum_options(EpsControlMode), options_enum=EpsControlMode, + value_fn=lambda device: device.eps.eps_control, write_fn=lambda device, value: device.eps.async_write_control( EpsControlMode(value) ), @@ -77,8 +80,7 @@ class SofarSelect(SofarEntity, SelectEntity): @override def current_option(self) -> str | None: """Return the currently selected option.""" - component = getattr(self.coordinator.device, self.entity_description.component) - value: IntEnum | None = getattr(component, self.entity_description.key) + value = self.entity_description.value_fn(self.coordinator.device) return value.name.lower() if value is not None else None @override diff --git a/homeassistant/components/sofar/sensor.py b/homeassistant/components/sofar/sensor.py index f10dadfd4a50..b74e083d96ce 100644 --- a/homeassistant/components/sofar/sensor.py +++ b/homeassistant/components/sofar/sensor.py @@ -1,11 +1,13 @@ """Support for Sofar sensors.""" -from collections.abc import Mapping +from collections.abc import Callable, Mapping from dataclasses import dataclass from datetime import date from enum import IntEnum from typing import cast, override +from sofar_modbus.model import CorrectedTotal +from sofar_modbus.modern.device import SofarInverter from sofar_modbus.modern.enums import FeedinLimitationMode, PassiveModeTimeoutAction from homeassistant.components.sensor import ( @@ -30,6 +32,7 @@ from homeassistant.const import ( ) from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback +from homeassistant.helpers.typing import StateType from .const import BATTERY_COMPONENTS, METER_ENERGY from .coordinator import SofarConfigEntry @@ -104,12 +107,11 @@ class SofarSensor(SofarEntity, SensorEntity): @property @override def native_value(self) -> str | int | float | date | None: - component = getattr(self.coordinator.device, self.entity_description.component) - value = getattr(component, self.entity_description.key) + value = self.entity_description.value_fn(self.coordinator.device) # IntEnum stringifies as the raw int; use the option slug. if isinstance(value, IntEnum): return value.name.lower() - return cast(str | int | float | date | None, value) + return value class SofarTotalSensor(SofarEntity, RestoreSensor): @@ -129,20 +131,13 @@ class SofarTotalSensor(SofarEntity, RestoreSensor): except ValueError, TypeError: return self._attr_native_value = val - if self.entity_description.state_class is SensorStateClass.TOTAL_INCREASING: - component = getattr( - self.coordinator.device, self.entity_description.component - ) - component.seed_high_water(self.entity_description.key, val) + if (total_fn := self.entity_description.total_fn) is not None: + total_fn(self.coordinator.device).seed(val) @property @override def native_value(self) -> int | float | None: - component = getattr(self.coordinator.device, self.entity_description.component) - if self.entity_description.state_class is SensorStateClass.TOTAL_INCREASING: - value = component.corrected(self.entity_description.key) - else: - value = getattr(component, self.entity_description.key) + value = self.entity_description.value_fn(self.coordinator.device) if isinstance(value, (int, float)): self._attr_native_value = value return cast(int | float | None, self._attr_native_value) @@ -152,6 +147,9 @@ class SofarTotalSensor(SofarEntity, RestoreSensor): class SofarSensorDescription(SensorEntityDescription, SofarEntityDescription): """Describe a Sofar sensor.""" + value_fn: Callable[[SofarInverter], StateType] + total_fn: Callable[[SofarInverter], CorrectedTotal] | None = None + @dataclass(frozen=True, kw_only=True) class _PartMeasurement: @@ -165,6 +163,7 @@ class _PartMeasurement: suggested_display_precision: int | None = None entity_category: EntityCategory | None = None entity_registry_enabled_default: bool = True + value_fn: Callable[[SofarInverter, int], StateType] # Which register block each string or pack is read from. @@ -188,6 +187,7 @@ _PV_STRING_MEASUREMENTS = ( device_class=SensorDeviceClass.VOLTAGE, native_unit_of_measurement=UnitOfElectricPotential.VOLT, entity_registry_enabled_default=False, + value_fn=lambda device, number: device.pv_string(number).voltage, ), _PartMeasurement( key="pv_current", @@ -196,6 +196,7 @@ _PV_STRING_MEASUREMENTS = ( native_unit_of_measurement=UnitOfElectricCurrent.AMPERE, suggested_display_precision=2, entity_registry_enabled_default=False, + value_fn=lambda device, number: device.pv_string(number).current, ), _PartMeasurement( key="pv_power", @@ -204,6 +205,7 @@ _PV_STRING_MEASUREMENTS = ( native_unit_of_measurement=UnitOfPower.KILO_WATT, state_class=SensorStateClass.MEASUREMENT, suggested_display_precision=2, + value_fn=lambda device, number: device.pv_string(number).power, ), ) @@ -213,6 +215,7 @@ _BATTERY_MEASUREMENTS = ( translation_key="voltage", device_class=SensorDeviceClass.VOLTAGE, native_unit_of_measurement=UnitOfElectricPotential.VOLT, + value_fn=lambda device, number: device.battery_string(number).voltage, ), _PartMeasurement( key="battery_current", @@ -220,6 +223,7 @@ _BATTERY_MEASUREMENTS = ( device_class=SensorDeviceClass.CURRENT, native_unit_of_measurement=UnitOfElectricCurrent.AMPERE, suggested_display_precision=2, + value_fn=lambda device, number: device.battery_string(number).current, ), _PartMeasurement( key="battery_power", @@ -228,6 +232,7 @@ _BATTERY_MEASUREMENTS = ( native_unit_of_measurement=UnitOfPower.KILO_WATT, state_class=SensorStateClass.MEASUREMENT, suggested_display_precision=2, + value_fn=lambda device, number: device.battery_string(number).power, ), _PartMeasurement( key="battery_temperature", @@ -236,6 +241,7 @@ _BATTERY_MEASUREMENTS = ( native_unit_of_measurement=UnitOfTemperature.CELSIUS, state_class=SensorStateClass.MEASUREMENT, entity_category=EntityCategory.DIAGNOSTIC, + value_fn=lambda device, number: device.battery_string(number).temperature, ), _PartMeasurement( key="battery_capacity", @@ -243,18 +249,21 @@ _BATTERY_MEASUREMENTS = ( device_class=SensorDeviceClass.BATTERY, native_unit_of_measurement=PERCENTAGE, state_class=SensorStateClass.MEASUREMENT, + value_fn=lambda device, number: device.battery_string(number).capacity, ), _PartMeasurement( key="battery_state_of_health", translation_key="state_of_health", native_unit_of_measurement=PERCENTAGE, entity_category=EntityCategory.DIAGNOSTIC, + value_fn=lambda device, number: device.battery_string(number).state_of_health, ), _PartMeasurement( key="battery_charge_cycle", translation_key="charge_cycle", state_class=SensorStateClass.MEASUREMENT, entity_category=EntityCategory.DIAGNOSTIC, + value_fn=lambda device, number: device.battery_string(number).charge_cycle, ), ) @@ -279,12 +288,20 @@ def _part_sensors( entity_registry_enabled_default=( measurement.entity_registry_enabled_default ), + value_fn=_part_value_fn(measurement.value_fn, number), ) for number, component in components.items() for measurement in measurements ) +def _part_value_fn( + value_fn: Callable[[SofarInverter, int], StateType], number: int +) -> Callable[[SofarInverter], StateType]: + """Bind a measurement to one numbered string or pack.""" + return lambda device: value_fn(device, number) + + SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( SofarSensorDescription( key="pv_power_total", @@ -293,6 +310,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( device_class=SensorDeviceClass.POWER, native_unit_of_measurement=UnitOfPower.KILO_WATT, state_class=SensorStateClass.MEASUREMENT, + value_fn=lambda device: device.pv_1_2.pv_power_total, ), SofarSensorDescription( key="solar_generation_total", @@ -302,6 +320,8 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, state_class=SensorStateClass.TOTAL_INCREASING, suggested_display_precision=2, + value_fn=lambda device: device.energy.solar_generation_total_corrected.value, + total_fn=lambda device: device.energy.solar_generation_total_corrected, ), SofarSensorDescription( key="system_state", @@ -318,6 +338,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( "upgrading", "self_charging", ], + value_fn=lambda device: device.state.system_state, ), SofarSensorDescription( key="inverter_temperature_1", @@ -327,6 +348,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( native_unit_of_measurement=UnitOfTemperature.CELSIUS, state_class=SensorStateClass.MEASUREMENT, entity_category=EntityCategory.DIAGNOSTIC, + value_fn=lambda device: device.state.inverter_temperature_1, ), SofarSensorDescription( key="inverter_temperature_2", @@ -336,6 +358,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( native_unit_of_measurement=UnitOfTemperature.CELSIUS, state_class=SensorStateClass.MEASUREMENT, entity_category=EntityCategory.DIAGNOSTIC, + value_fn=lambda device: device.state.inverter_temperature_2, ), SofarSensorDescription( key="heatsink_temperature_1", @@ -345,6 +368,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( native_unit_of_measurement=UnitOfTemperature.CELSIUS, state_class=SensorStateClass.MEASUREMENT, entity_category=EntityCategory.DIAGNOSTIC, + value_fn=lambda device: device.state.heatsink_temperature_1, ), SofarSensorDescription( key="heatsink_temperature_2", @@ -354,6 +378,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( native_unit_of_measurement=UnitOfTemperature.CELSIUS, state_class=SensorStateClass.MEASUREMENT, entity_category=EntityCategory.DIAGNOSTIC, + value_fn=lambda device: device.state.heatsink_temperature_2, ), SofarSensorDescription( key="module_temperature_1", @@ -363,6 +388,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( native_unit_of_measurement=UnitOfTemperature.CELSIUS, state_class=SensorStateClass.MEASUREMENT, entity_category=EntityCategory.DIAGNOSTIC, + value_fn=lambda device: device.state.module_temperature_1, ), SofarSensorDescription( key="module_temperature_2", @@ -372,6 +398,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( native_unit_of_measurement=UnitOfTemperature.CELSIUS, state_class=SensorStateClass.MEASUREMENT, entity_category=EntityCategory.DIAGNOSTIC, + value_fn=lambda device: device.state.module_temperature_2, ), SofarSensorDescription( key="grid_frequency", @@ -380,6 +407,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( device_class=SensorDeviceClass.FREQUENCY, native_unit_of_measurement=UnitOfFrequency.HERTZ, suggested_display_precision=2, + value_fn=lambda device: device.grid.grid_frequency, ), SofarSensorDescription( key="active_power_output_total", @@ -389,6 +417,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( native_unit_of_measurement=UnitOfPower.KILO_WATT, state_class=SensorStateClass.MEASUREMENT, suggested_display_precision=2, + value_fn=lambda device: device.grid.active_power_output_total, ), SofarSensorDescription( key="reactive_power_output_total", @@ -399,6 +428,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( state_class=SensorStateClass.MEASUREMENT, entity_registry_enabled_default=False, suggested_display_precision=2, + value_fn=lambda device: device.grid.reactive_power_output_total, ), SofarSensorDescription( key="apparent_power_output_total", @@ -408,6 +438,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( native_unit_of_measurement=UnitOfApparentPower.KILO_VOLT_AMPERE, state_class=SensorStateClass.MEASUREMENT, suggested_display_precision=2, + value_fn=lambda device: device.grid.apparent_power_output_total, ), SofarSensorDescription( key="active_power_pcc_total", @@ -417,6 +448,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( native_unit_of_measurement=UnitOfPower.KILO_WATT, state_class=SensorStateClass.MEASUREMENT, suggested_display_precision=2, + value_fn=lambda device: device.grid.active_power_pcc_total, ), SofarSensorDescription( key="reactive_power_pcc_total", @@ -427,6 +459,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( state_class=SensorStateClass.MEASUREMENT, entity_registry_enabled_default=False, suggested_display_precision=2, + value_fn=lambda device: device.grid.reactive_power_pcc_total, ), SofarSensorDescription( key="apparent_power_pcc_total", @@ -436,6 +469,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( native_unit_of_measurement=UnitOfApparentPower.KILO_VOLT_AMPERE, state_class=SensorStateClass.MEASUREMENT, suggested_display_precision=2, + value_fn=lambda device: device.grid.apparent_power_pcc_total, ), SofarSensorDescription( key="voltage_l1", @@ -444,6 +478,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( device_class=SensorDeviceClass.VOLTAGE, native_unit_of_measurement=UnitOfElectricPotential.VOLT, entity_registry_enabled_default=False, + value_fn=lambda device: device.grid.voltage_l1, ), SofarSensorDescription( key="current_output_l1", @@ -453,6 +488,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( native_unit_of_measurement=UnitOfElectricCurrent.AMPERE, suggested_display_precision=2, entity_registry_enabled_default=False, + value_fn=lambda device: device.grid.current_output_l1, ), SofarSensorDescription( key="active_power_output_l1", @@ -462,6 +498,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( native_unit_of_measurement=UnitOfPower.KILO_WATT, suggested_display_precision=2, entity_registry_enabled_default=False, + value_fn=lambda device: device.grid.active_power_output_l1, ), SofarSensorDescription( key="reactive_power_output_l1", @@ -471,6 +508,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( native_unit_of_measurement=UnitOfReactivePower.KILO_VOLT_AMPERE_REACTIVE, entity_registry_enabled_default=False, suggested_display_precision=2, + value_fn=lambda device: device.grid.reactive_power_output_l1, ), SofarSensorDescription( key="power_factor_output_l1", @@ -479,6 +517,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( device_class=SensorDeviceClass.POWER_FACTOR, state_class=SensorStateClass.MEASUREMENT, entity_registry_enabled_default=False, + value_fn=lambda device: device.grid.power_factor_output_l1, ), SofarSensorDescription( key="current_pcc_l1", @@ -488,6 +527,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( native_unit_of_measurement=UnitOfElectricCurrent.AMPERE, suggested_display_precision=2, entity_registry_enabled_default=False, + value_fn=lambda device: device.grid.current_pcc_l1, ), SofarSensorDescription( key="active_power_pcc_l1", @@ -497,6 +537,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( native_unit_of_measurement=UnitOfPower.KILO_WATT, suggested_display_precision=2, entity_registry_enabled_default=False, + value_fn=lambda device: device.grid.active_power_pcc_l1, ), SofarSensorDescription( key="reactive_power_pcc_l1", @@ -506,6 +547,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( native_unit_of_measurement=UnitOfReactivePower.KILO_VOLT_AMPERE_REACTIVE, entity_registry_enabled_default=False, suggested_display_precision=2, + value_fn=lambda device: device.grid.reactive_power_pcc_l1, ), SofarSensorDescription( key="power_factor_pcc_l1", @@ -514,6 +556,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( device_class=SensorDeviceClass.POWER_FACTOR, state_class=SensorStateClass.MEASUREMENT, entity_registry_enabled_default=False, + value_fn=lambda device: device.grid.power_factor_pcc_l1, ), SofarSensorDescription( key="voltage_l2", @@ -522,6 +565,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( device_class=SensorDeviceClass.VOLTAGE, native_unit_of_measurement=UnitOfElectricPotential.VOLT, entity_registry_enabled_default=False, + value_fn=lambda device: device.grid.voltage_l2, ), SofarSensorDescription( key="current_output_l2", @@ -531,6 +575,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( native_unit_of_measurement=UnitOfElectricCurrent.AMPERE, suggested_display_precision=2, entity_registry_enabled_default=False, + value_fn=lambda device: device.grid.current_output_l2, ), SofarSensorDescription( key="active_power_output_l2", @@ -540,6 +585,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( native_unit_of_measurement=UnitOfPower.KILO_WATT, suggested_display_precision=2, entity_registry_enabled_default=False, + value_fn=lambda device: device.grid.active_power_output_l2, ), SofarSensorDescription( key="reactive_power_output_l2", @@ -549,6 +595,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( native_unit_of_measurement=UnitOfReactivePower.KILO_VOLT_AMPERE_REACTIVE, entity_registry_enabled_default=False, suggested_display_precision=2, + value_fn=lambda device: device.grid.reactive_power_output_l2, ), SofarSensorDescription( key="power_factor_output_l2", @@ -557,6 +604,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( device_class=SensorDeviceClass.POWER_FACTOR, state_class=SensorStateClass.MEASUREMENT, entity_registry_enabled_default=False, + value_fn=lambda device: device.grid.power_factor_output_l2, ), SofarSensorDescription( key="current_pcc_l2", @@ -566,6 +614,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( native_unit_of_measurement=UnitOfElectricCurrent.AMPERE, suggested_display_precision=2, entity_registry_enabled_default=False, + value_fn=lambda device: device.grid.current_pcc_l2, ), SofarSensorDescription( key="active_power_pcc_l2", @@ -575,6 +624,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( native_unit_of_measurement=UnitOfPower.KILO_WATT, suggested_display_precision=2, entity_registry_enabled_default=False, + value_fn=lambda device: device.grid.active_power_pcc_l2, ), SofarSensorDescription( key="reactive_power_pcc_l2", @@ -584,6 +634,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( native_unit_of_measurement=UnitOfReactivePower.KILO_VOLT_AMPERE_REACTIVE, entity_registry_enabled_default=False, suggested_display_precision=2, + value_fn=lambda device: device.grid.reactive_power_pcc_l2, ), SofarSensorDescription( key="power_factor_pcc_l2", @@ -592,6 +643,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( device_class=SensorDeviceClass.POWER_FACTOR, state_class=SensorStateClass.MEASUREMENT, entity_registry_enabled_default=False, + value_fn=lambda device: device.grid.power_factor_pcc_l2, ), SofarSensorDescription( key="voltage_l3", @@ -600,6 +652,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( device_class=SensorDeviceClass.VOLTAGE, native_unit_of_measurement=UnitOfElectricPotential.VOLT, entity_registry_enabled_default=False, + value_fn=lambda device: device.grid.voltage_l3, ), SofarSensorDescription( key="current_output_l3", @@ -609,6 +662,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( native_unit_of_measurement=UnitOfElectricCurrent.AMPERE, suggested_display_precision=2, entity_registry_enabled_default=False, + value_fn=lambda device: device.grid.current_output_l3, ), SofarSensorDescription( key="active_power_output_l3", @@ -618,6 +672,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( native_unit_of_measurement=UnitOfPower.KILO_WATT, suggested_display_precision=2, entity_registry_enabled_default=False, + value_fn=lambda device: device.grid.active_power_output_l3, ), SofarSensorDescription( key="reactive_power_output_l3", @@ -627,6 +682,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( native_unit_of_measurement=UnitOfReactivePower.KILO_VOLT_AMPERE_REACTIVE, entity_registry_enabled_default=False, suggested_display_precision=2, + value_fn=lambda device: device.grid.reactive_power_output_l3, ), SofarSensorDescription( key="power_factor_output_l3", @@ -635,6 +691,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( device_class=SensorDeviceClass.POWER_FACTOR, state_class=SensorStateClass.MEASUREMENT, entity_registry_enabled_default=False, + value_fn=lambda device: device.grid.power_factor_output_l3, ), SofarSensorDescription( key="current_pcc_l3", @@ -644,6 +701,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( native_unit_of_measurement=UnitOfElectricCurrent.AMPERE, suggested_display_precision=2, entity_registry_enabled_default=False, + value_fn=lambda device: device.grid.current_pcc_l3, ), SofarSensorDescription( key="active_power_pcc_l3", @@ -653,6 +711,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( native_unit_of_measurement=UnitOfPower.KILO_WATT, suggested_display_precision=2, entity_registry_enabled_default=False, + value_fn=lambda device: device.grid.active_power_pcc_l3, ), SofarSensorDescription( key="reactive_power_pcc_l3", @@ -662,6 +721,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( native_unit_of_measurement=UnitOfReactivePower.KILO_VOLT_AMPERE_REACTIVE, entity_registry_enabled_default=False, suggested_display_precision=2, + value_fn=lambda device: device.grid.reactive_power_pcc_l3, ), SofarSensorDescription( key="power_factor_pcc_l3", @@ -670,6 +730,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( device_class=SensorDeviceClass.POWER_FACTOR, state_class=SensorStateClass.MEASUREMENT, entity_registry_enabled_default=False, + value_fn=lambda device: device.grid.power_factor_pcc_l3, ), SofarSensorDescription( key="active_power_pv_ext", @@ -679,6 +740,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( native_unit_of_measurement=UnitOfPower.KILO_WATT, state_class=SensorStateClass.MEASUREMENT, suggested_display_precision=2, + value_fn=lambda device: device.grid.active_power_pv_ext, ), SofarSensorDescription( key="active_power_load_sys", @@ -688,6 +750,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( native_unit_of_measurement=UnitOfPower.KILO_WATT, state_class=SensorStateClass.MEASUREMENT, suggested_display_precision=2, + value_fn=lambda device: device.grid.active_power_load_sys, ), SofarSensorDescription( key="voltage_phase_l1n", @@ -696,6 +759,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( device_class=SensorDeviceClass.VOLTAGE, native_unit_of_measurement=UnitOfElectricPotential.VOLT, entity_registry_enabled_default=False, + value_fn=lambda device: device.grid.voltage_phase_l1n, ), SofarSensorDescription( key="current_output_l1n", @@ -705,6 +769,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( native_unit_of_measurement=UnitOfElectricCurrent.AMPERE, suggested_display_precision=2, entity_registry_enabled_default=False, + value_fn=lambda device: device.grid.current_output_l1n, ), SofarSensorDescription( key="active_power_output_l1n", @@ -714,6 +779,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( native_unit_of_measurement=UnitOfPower.KILO_WATT, suggested_display_precision=2, entity_registry_enabled_default=False, + value_fn=lambda device: device.grid.active_power_output_l1n, ), SofarSensorDescription( key="current_pcc_l1n", @@ -723,6 +789,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( native_unit_of_measurement=UnitOfElectricCurrent.AMPERE, suggested_display_precision=2, entity_registry_enabled_default=False, + value_fn=lambda device: device.grid.current_pcc_l1n, ), SofarSensorDescription( key="active_power_pcc_l1n", @@ -732,6 +799,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( native_unit_of_measurement=UnitOfPower.KILO_WATT, suggested_display_precision=2, entity_registry_enabled_default=False, + value_fn=lambda device: device.grid.active_power_pcc_l1n, ), SofarSensorDescription( key="voltage_phase_l2n", @@ -740,6 +808,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( device_class=SensorDeviceClass.VOLTAGE, native_unit_of_measurement=UnitOfElectricPotential.VOLT, entity_registry_enabled_default=False, + value_fn=lambda device: device.grid.voltage_phase_l2n, ), SofarSensorDescription( key="current_output_l2n", @@ -749,6 +818,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( native_unit_of_measurement=UnitOfElectricCurrent.AMPERE, suggested_display_precision=2, entity_registry_enabled_default=False, + value_fn=lambda device: device.grid.current_output_l2n, ), SofarSensorDescription( key="active_power_output_l2n", @@ -758,6 +828,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( native_unit_of_measurement=UnitOfPower.KILO_WATT, suggested_display_precision=2, entity_registry_enabled_default=False, + value_fn=lambda device: device.grid.active_power_output_l2n, ), SofarSensorDescription( key="current_pcc_l2n", @@ -767,6 +838,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( native_unit_of_measurement=UnitOfElectricCurrent.AMPERE, suggested_display_precision=2, entity_registry_enabled_default=False, + value_fn=lambda device: device.grid.current_pcc_l2n, ), SofarSensorDescription( key="active_power_pcc_l2n", @@ -776,6 +848,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( native_unit_of_measurement=UnitOfPower.KILO_WATT, suggested_display_precision=2, entity_registry_enabled_default=False, + value_fn=lambda device: device.grid.active_power_pcc_l2n, ), SofarSensorDescription( key="voltage_line_l1", @@ -784,6 +857,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( device_class=SensorDeviceClass.VOLTAGE, native_unit_of_measurement=UnitOfElectricPotential.VOLT, entity_registry_enabled_default=False, + value_fn=lambda device: device.grid.voltage_line_l1, ), SofarSensorDescription( key="voltage_line_l2", @@ -792,6 +866,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( device_class=SensorDeviceClass.VOLTAGE, native_unit_of_measurement=UnitOfElectricPotential.VOLT, entity_registry_enabled_default=False, + value_fn=lambda device: device.grid.voltage_line_l2, ), SofarSensorDescription( key="voltage_line_l3", @@ -800,6 +875,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( device_class=SensorDeviceClass.VOLTAGE, native_unit_of_measurement=UnitOfElectricPotential.VOLT, entity_registry_enabled_default=False, + value_fn=lambda device: device.grid.voltage_line_l3, ), SofarSensorDescription( key="active_power_offgrid_total", @@ -809,6 +885,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( native_unit_of_measurement=UnitOfPower.KILO_WATT, state_class=SensorStateClass.MEASUREMENT, suggested_display_precision=2, + value_fn=lambda device: device.offgrid.active_power_offgrid_total, ), SofarSensorDescription( key="reactive_power_offgrid_total", @@ -819,6 +896,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( state_class=SensorStateClass.MEASUREMENT, entity_registry_enabled_default=False, suggested_display_precision=2, + value_fn=lambda device: device.offgrid.reactive_power_offgrid_total, ), SofarSensorDescription( key="apparent_power_offgrid_total", @@ -828,6 +906,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( native_unit_of_measurement=UnitOfApparentPower.KILO_VOLT_AMPERE, state_class=SensorStateClass.MEASUREMENT, suggested_display_precision=2, + value_fn=lambda device: device.offgrid.apparent_power_offgrid_total, ), SofarSensorDescription( key="offgrid_frequency", @@ -836,6 +915,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( device_class=SensorDeviceClass.FREQUENCY, native_unit_of_measurement=UnitOfFrequency.HERTZ, suggested_display_precision=2, + value_fn=lambda device: device.offgrid.offgrid_frequency, ), SofarSensorDescription( key="offgrid_voltage", @@ -844,6 +924,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( device_class=SensorDeviceClass.VOLTAGE, native_unit_of_measurement=UnitOfElectricPotential.VOLT, state_class=SensorStateClass.MEASUREMENT, + value_fn=lambda device: device.offgrid_single_phase.offgrid_voltage, ), SofarSensorDescription( key="offgrid_voltage_l1", @@ -853,6 +934,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( native_unit_of_measurement=UnitOfElectricPotential.VOLT, state_class=SensorStateClass.MEASUREMENT, entity_registry_enabled_default=False, + value_fn=lambda device: device.offgrid_three_phase.offgrid_voltage_l1, ), SofarSensorDescription( key="offgrid_current_output", @@ -862,6 +944,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( native_unit_of_measurement=UnitOfElectricCurrent.AMPERE, state_class=SensorStateClass.MEASUREMENT, suggested_display_precision=2, + value_fn=lambda device: device.offgrid_single_phase.offgrid_current_output, ), SofarSensorDescription( key="offgrid_current_output_l1", @@ -872,6 +955,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( state_class=SensorStateClass.MEASUREMENT, suggested_display_precision=2, entity_registry_enabled_default=False, + value_fn=lambda device: device.offgrid_three_phase.offgrid_current_output_l1, ), SofarSensorDescription( key="offgrid_active_power_output", @@ -881,6 +965,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( native_unit_of_measurement=UnitOfPower.KILO_WATT, state_class=SensorStateClass.MEASUREMENT, suggested_display_precision=2, + value_fn=lambda device: device.offgrid_single_phase.offgrid_active_power_output, ), SofarSensorDescription( key="offgrid_active_power_output_l1", @@ -891,6 +976,9 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( state_class=SensorStateClass.MEASUREMENT, suggested_display_precision=2, entity_registry_enabled_default=False, + value_fn=lambda device: ( + device.offgrid_three_phase.offgrid_active_power_output_l1 + ), ), SofarSensorDescription( key="offgrid_reactive_power_output", @@ -901,6 +989,9 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( state_class=SensorStateClass.MEASUREMENT, entity_registry_enabled_default=False, suggested_display_precision=2, + value_fn=lambda device: ( + device.offgrid_single_phase.offgrid_reactive_power_output + ), ), SofarSensorDescription( key="offgrid_reactive_power_output_l1", @@ -911,6 +1002,9 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( state_class=SensorStateClass.MEASUREMENT, entity_registry_enabled_default=False, suggested_display_precision=2, + value_fn=lambda device: ( + device.offgrid_three_phase.offgrid_reactive_power_output_l1 + ), ), SofarSensorDescription( key="offgrid_apparent_power_output", @@ -920,6 +1014,9 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( native_unit_of_measurement=UnitOfApparentPower.KILO_VOLT_AMPERE, state_class=SensorStateClass.MEASUREMENT, suggested_display_precision=2, + value_fn=lambda device: ( + device.offgrid_single_phase.offgrid_apparent_power_output + ), ), SofarSensorDescription( key="offgrid_apparent_power_output_l1", @@ -930,6 +1027,9 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( state_class=SensorStateClass.MEASUREMENT, suggested_display_precision=2, entity_registry_enabled_default=False, + value_fn=lambda device: ( + device.offgrid_three_phase.offgrid_apparent_power_output_l1 + ), ), SofarSensorDescription( key="offgrid_load_peak_ratio", @@ -937,6 +1037,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( translation_key="offgrid_load_peak_ratio", state_class=SensorStateClass.MEASUREMENT, suggested_display_precision=2, + value_fn=lambda device: device.offgrid_single_phase.offgrid_load_peak_ratio, ), SofarSensorDescription( key="offgrid_load_peak_ratio_l1", @@ -945,6 +1046,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( state_class=SensorStateClass.MEASUREMENT, suggested_display_precision=2, entity_registry_enabled_default=False, + value_fn=lambda device: device.offgrid_three_phase.offgrid_load_peak_ratio_l1, ), SofarSensorDescription( key="offgrid_voltage_l2", @@ -954,6 +1056,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( native_unit_of_measurement=UnitOfElectricPotential.VOLT, state_class=SensorStateClass.MEASUREMENT, entity_registry_enabled_default=False, + value_fn=lambda device: device.offgrid_three_phase.offgrid_voltage_l2, ), SofarSensorDescription( key="offgrid_current_output_l2", @@ -964,6 +1067,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( state_class=SensorStateClass.MEASUREMENT, suggested_display_precision=2, entity_registry_enabled_default=False, + value_fn=lambda device: device.offgrid_three_phase.offgrid_current_output_l2, ), SofarSensorDescription( key="offgrid_active_power_output_l2", @@ -974,6 +1078,9 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( state_class=SensorStateClass.MEASUREMENT, suggested_display_precision=2, entity_registry_enabled_default=False, + value_fn=lambda device: ( + device.offgrid_three_phase.offgrid_active_power_output_l2 + ), ), SofarSensorDescription( key="offgrid_reactive_power_output_l2", @@ -984,6 +1091,9 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( state_class=SensorStateClass.MEASUREMENT, entity_registry_enabled_default=False, suggested_display_precision=2, + value_fn=lambda device: ( + device.offgrid_three_phase.offgrid_reactive_power_output_l2 + ), ), SofarSensorDescription( key="offgrid_apparent_power_output_l2", @@ -994,6 +1104,9 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( state_class=SensorStateClass.MEASUREMENT, suggested_display_precision=2, entity_registry_enabled_default=False, + value_fn=lambda device: ( + device.offgrid_three_phase.offgrid_apparent_power_output_l2 + ), ), SofarSensorDescription( key="offgrid_load_peak_ratio_l2", @@ -1002,6 +1115,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( state_class=SensorStateClass.MEASUREMENT, suggested_display_precision=2, entity_registry_enabled_default=False, + value_fn=lambda device: device.offgrid_three_phase.offgrid_load_peak_ratio_l2, ), SofarSensorDescription( key="offgrid_voltage_l3", @@ -1011,6 +1125,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( native_unit_of_measurement=UnitOfElectricPotential.VOLT, state_class=SensorStateClass.MEASUREMENT, entity_registry_enabled_default=False, + value_fn=lambda device: device.offgrid_three_phase.offgrid_voltage_l3, ), SofarSensorDescription( key="offgrid_current_output_l3", @@ -1021,6 +1136,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( state_class=SensorStateClass.MEASUREMENT, suggested_display_precision=2, entity_registry_enabled_default=False, + value_fn=lambda device: device.offgrid_three_phase.offgrid_current_output_l3, ), SofarSensorDescription( key="offgrid_active_power_output_l3", @@ -1031,6 +1147,9 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( state_class=SensorStateClass.MEASUREMENT, suggested_display_precision=2, entity_registry_enabled_default=False, + value_fn=lambda device: ( + device.offgrid_three_phase.offgrid_active_power_output_l3 + ), ), SofarSensorDescription( key="offgrid_reactive_power_output_l3", @@ -1041,6 +1160,9 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( state_class=SensorStateClass.MEASUREMENT, entity_registry_enabled_default=False, suggested_display_precision=2, + value_fn=lambda device: ( + device.offgrid_three_phase.offgrid_reactive_power_output_l3 + ), ), SofarSensorDescription( key="offgrid_apparent_power_output_l3", @@ -1051,6 +1173,9 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( state_class=SensorStateClass.MEASUREMENT, suggested_display_precision=2, entity_registry_enabled_default=False, + value_fn=lambda device: ( + device.offgrid_three_phase.offgrid_apparent_power_output_l3 + ), ), SofarSensorDescription( key="offgrid_load_peak_ratio_l3", @@ -1059,6 +1184,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( state_class=SensorStateClass.MEASUREMENT, suggested_display_precision=2, entity_registry_enabled_default=False, + value_fn=lambda device: device.offgrid_three_phase.offgrid_load_peak_ratio_l3, ), SofarSensorDescription( key="offgrid_voltage_output_l1n", @@ -1068,6 +1194,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( native_unit_of_measurement=UnitOfElectricPotential.VOLT, state_class=SensorStateClass.MEASUREMENT, entity_registry_enabled_default=False, + value_fn=lambda device: device.offgrid_three_phase.offgrid_voltage_output_l1n, ), SofarSensorDescription( key="offgrid_current_output_l1n", @@ -1078,6 +1205,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( state_class=SensorStateClass.MEASUREMENT, suggested_display_precision=2, entity_registry_enabled_default=False, + value_fn=lambda device: device.offgrid_three_phase.offgrid_current_output_l1n, ), SofarSensorDescription( key="offgrid_active_power_output_l1n", @@ -1088,6 +1216,9 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( state_class=SensorStateClass.MEASUREMENT, suggested_display_precision=2, entity_registry_enabled_default=False, + value_fn=lambda device: ( + device.offgrid_three_phase.offgrid_active_power_output_l1n + ), ), SofarSensorDescription( key="offgrid_voltage_output_l2n", @@ -1097,6 +1228,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( native_unit_of_measurement=UnitOfElectricPotential.VOLT, state_class=SensorStateClass.MEASUREMENT, entity_registry_enabled_default=False, + value_fn=lambda device: device.offgrid_three_phase.offgrid_voltage_output_l2n, ), SofarSensorDescription( key="offgrid_current_output_l2n", @@ -1107,6 +1239,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( state_class=SensorStateClass.MEASUREMENT, suggested_display_precision=2, entity_registry_enabled_default=False, + value_fn=lambda device: device.offgrid_three_phase.offgrid_current_output_l2n, ), SofarSensorDescription( key="offgrid_active_power_output_l2n", @@ -1117,6 +1250,9 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( state_class=SensorStateClass.MEASUREMENT, suggested_display_precision=2, entity_registry_enabled_default=False, + value_fn=lambda device: ( + device.offgrid_three_phase.offgrid_active_power_output_l2n + ), ), SofarSensorDescription( key="battery_power_total", @@ -1125,6 +1261,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( device_class=SensorDeviceClass.POWER, native_unit_of_measurement=UnitOfPower.KILO_WATT, state_class=SensorStateClass.MEASUREMENT, + value_fn=lambda device: device.battery_totals.battery_power_total, ), SofarSensorDescription( key="battery_capacity_total", @@ -1133,6 +1270,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( device_class=SensorDeviceClass.BATTERY, native_unit_of_measurement=PERCENTAGE, state_class=SensorStateClass.MEASUREMENT, + value_fn=lambda device: device.battery_totals.battery_capacity_total, ), SofarSensorDescription( key="battery_state_of_health_total", @@ -1140,6 +1278,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( translation_key="battery_state_of_health_total", native_unit_of_measurement=PERCENTAGE, entity_category=EntityCategory.DIAGNOSTIC, + value_fn=lambda device: device.battery_totals.battery_state_of_health_total, ), SofarSensorDescription( key="solar_generation_today", @@ -1150,6 +1289,8 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( state_class=SensorStateClass.TOTAL_INCREASING, suggested_display_precision=2, entity_registry_enabled_default=False, + value_fn=lambda device: device.energy.solar_generation_today_corrected.value, + total_fn=lambda device: device.energy.solar_generation_today_corrected, ), SofarSensorDescription( key="load_consumption_today", @@ -1160,6 +1301,10 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( state_class=SensorStateClass.TOTAL_INCREASING, suggested_display_precision=2, entity_registry_enabled_default=False, + value_fn=lambda device: ( + device.meter_energy.load_consumption_today_corrected.value + ), + total_fn=lambda device: device.meter_energy.load_consumption_today_corrected, ), SofarSensorDescription( key="load_consumption_total", @@ -1169,6 +1314,10 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, state_class=SensorStateClass.TOTAL_INCREASING, suggested_display_precision=2, + value_fn=lambda device: ( + device.meter_energy.load_consumption_total_corrected.value + ), + total_fn=lambda device: device.meter_energy.load_consumption_total_corrected, ), SofarSensorDescription( key="import_energy_today", @@ -1179,6 +1328,8 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( state_class=SensorStateClass.TOTAL_INCREASING, suggested_display_precision=2, entity_registry_enabled_default=False, + value_fn=lambda device: device.meter_energy.import_energy_today_corrected.value, + total_fn=lambda device: device.meter_energy.import_energy_today_corrected, ), SofarSensorDescription( key="import_energy_total", @@ -1188,6 +1339,8 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, state_class=SensorStateClass.TOTAL_INCREASING, suggested_display_precision=2, + value_fn=lambda device: device.meter_energy.import_energy_total_corrected.value, + total_fn=lambda device: device.meter_energy.import_energy_total_corrected, ), SofarSensorDescription( key="export_energy_today", @@ -1198,6 +1351,8 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( state_class=SensorStateClass.TOTAL_INCREASING, suggested_display_precision=2, entity_registry_enabled_default=False, + value_fn=lambda device: device.meter_energy.export_energy_today_corrected.value, + total_fn=lambda device: device.meter_energy.export_energy_today_corrected, ), SofarSensorDescription( key="export_energy_total", @@ -1207,6 +1362,8 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, state_class=SensorStateClass.TOTAL_INCREASING, suggested_display_precision=2, + value_fn=lambda device: device.meter_energy.export_energy_total_corrected.value, + total_fn=lambda device: device.meter_energy.export_energy_total_corrected, ), SofarSensorDescription( key="battery_input_energy_today", @@ -1217,6 +1374,12 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( state_class=SensorStateClass.TOTAL_INCREASING, suggested_display_precision=2, entity_registry_enabled_default=False, + value_fn=lambda device: ( + device.battery_energy.battery_input_energy_today_corrected.value + ), + total_fn=lambda device: ( + device.battery_energy.battery_input_energy_today_corrected + ), ), SofarSensorDescription( key="battery_input_energy_total", @@ -1226,6 +1389,12 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, state_class=SensorStateClass.TOTAL_INCREASING, suggested_display_precision=2, + value_fn=lambda device: ( + device.battery_energy.battery_input_energy_total_corrected.value + ), + total_fn=lambda device: ( + device.battery_energy.battery_input_energy_total_corrected + ), ), SofarSensorDescription( key="battery_output_energy_today", @@ -1236,6 +1405,12 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( state_class=SensorStateClass.TOTAL_INCREASING, suggested_display_precision=2, entity_registry_enabled_default=False, + value_fn=lambda device: ( + device.battery_energy.battery_output_energy_today_corrected.value + ), + total_fn=lambda device: ( + device.battery_energy.battery_output_energy_today_corrected + ), ), SofarSensorDescription( key="battery_output_energy_total", @@ -1245,6 +1420,12 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, state_class=SensorStateClass.TOTAL_INCREASING, suggested_display_precision=2, + value_fn=lambda device: ( + device.battery_energy.battery_output_energy_total_corrected.value + ), + total_fn=lambda device: ( + device.battery_energy.battery_output_energy_total_corrected + ), ), SofarSensorDescription( key="passive_eps_wait_time", @@ -1254,6 +1435,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( native_unit_of_measurement=UnitOfTime.SECONDS, entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, + value_fn=lambda device: device.eps.passive_eps_wait_time, ), SofarSensorDescription( key="bat_config_protocol", @@ -1277,6 +1459,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( ], entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, + value_fn=lambda device: device.battery_config_id.bat_config_protocol, ), SofarSensorDescription( key="bat_config_overvoltage_protection", @@ -1286,6 +1469,9 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( native_unit_of_measurement=UnitOfElectricPotential.VOLT, entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, + value_fn=lambda device: ( + device.battery_config_id.bat_config_overvoltage_protection + ), ), SofarSensorDescription( key="bat_config_charging_voltage", @@ -1295,6 +1481,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( native_unit_of_measurement=UnitOfElectricPotential.VOLT, entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, + value_fn=lambda device: device.battery_config.bat_config_charging_voltage, ), SofarSensorDescription( key="bat_config_undervoltage_protection", @@ -1304,6 +1491,9 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( native_unit_of_measurement=UnitOfElectricPotential.VOLT, entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, + value_fn=lambda device: ( + device.battery_config.bat_config_undervoltage_protection + ), ), SofarSensorDescription( key="bat_config_minimum_discharge_voltage", @@ -1313,6 +1503,9 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( native_unit_of_measurement=UnitOfElectricPotential.VOLT, entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, + value_fn=lambda device: ( + device.battery_config.bat_config_minimum_discharge_voltage + ), ), SofarSensorDescription( key="bat_config_maximum_charge_current_limit", @@ -1323,6 +1516,9 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, suggested_display_precision=2, + value_fn=lambda device: ( + device.battery_config.bat_config_maximum_charge_current_limit + ), ), SofarSensorDescription( key="bat_config_maximum_discharge_current_limit", @@ -1333,6 +1529,9 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, suggested_display_precision=2, + value_fn=lambda device: ( + device.battery_config.bat_config_maximum_discharge_current_limit + ), ), SofarSensorDescription( key="bat_config_depth_of_discharge", @@ -1341,6 +1540,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( native_unit_of_measurement=PERCENTAGE, entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, + value_fn=lambda device: device.battery_config.bat_config_depth_of_discharge, ), SofarSensorDescription( key="bat_config_end_of_discharge", @@ -1349,6 +1549,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( native_unit_of_measurement=PERCENTAGE, entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, + value_fn=lambda device: device.battery_config.bat_config_end_of_discharge, ), SofarSensorDescription( key="bat_config_capacity", @@ -1357,6 +1558,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( native_unit_of_measurement="Ah", entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, + value_fn=lambda device: device.battery_config.bat_config_capacity, ), SofarSensorDescription( key="bat_config_rated_battery_voltage", @@ -1366,6 +1568,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( native_unit_of_measurement=UnitOfElectricPotential.VOLT, entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, + value_fn=lambda device: device.battery_config.bat_config_rated_battery_voltage, ), SofarSensorDescription( key="bat_config_cell_type", @@ -1383,6 +1586,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( ], entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, + value_fn=lambda device: device.battery_config.bat_config_cell_type, ), SofarSensorDescription( key="bat_config_eps_buffer", @@ -1391,6 +1595,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( native_unit_of_measurement=PERCENTAGE, entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, + value_fn=lambda device: device.battery_config.bat_config_eps_buffer, ), SofarSensorDescription( key="bat_config_tempco", @@ -1399,6 +1604,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( native_unit_of_measurement="mV/Cell", entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, + value_fn=lambda device: device.battery_config.bat_config_tempco, ), SofarSensorDescription( key="bat_config_voltage_float", @@ -1408,6 +1614,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( native_unit_of_measurement=UnitOfElectricPotential.VOLT, entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, + value_fn=lambda device: device.battery_config.bat_config_voltage_float, ), SofarSensorDescription( key="sync_rtc_result", @@ -1426,6 +1633,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( "operation_failed_input_parameters_incorrect", ], entity_category=EntityCategory.DIAGNOSTIC, + value_fn=lambda device: device.rtc_sync.sync_rtc_result, ), SofarSensorDescription( key="feedin_limitation_mode", @@ -1435,6 +1643,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( options=[mode.name.lower() for mode in FeedinLimitationMode], entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, + value_fn=lambda device: device.feed_in.feedin_limitation_mode, ), SofarSensorDescription( key="feedin_max_power", @@ -1444,6 +1653,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( native_unit_of_measurement=UnitOfPower.WATT, entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, + value_fn=lambda device: device.feed_in.feedin_max_power, ), SofarSensorDescription( key="active_power_export_limit", @@ -1452,6 +1662,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( native_unit_of_measurement=PERCENTAGE, entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, + value_fn=lambda device: device.active_power_control.active_power_export_limit, ), SofarSensorDescription( key="passive_mode_timeout", @@ -1461,6 +1672,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( native_unit_of_measurement=UnitOfTime.SECONDS, entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, + value_fn=lambda device: device.passive.passive_mode_timeout, ), SofarSensorDescription( key="passive_mode_timeout_action", @@ -1470,6 +1682,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( options=[action.name.lower() for action in PassiveModeTimeoutAction], entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, + value_fn=lambda device: device.passive.passive_mode_timeout_action, ), SofarSensorDescription( key="passive_mode_grid_power", @@ -1479,6 +1692,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( native_unit_of_measurement=UnitOfPower.WATT, entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, + value_fn=lambda device: device.passive.passive_mode_grid_power, ), SofarSensorDescription( key="passive_mode_battery_power_min", @@ -1488,6 +1702,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( native_unit_of_measurement=UnitOfPower.WATT, entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, + value_fn=lambda device: device.passive.passive_mode_battery_power_min, ), SofarSensorDescription( key="passive_mode_battery_power_max", @@ -1497,6 +1712,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = ( native_unit_of_measurement=UnitOfPower.WATT, entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, + value_fn=lambda device: device.passive.passive_mode_battery_power_max, ), ) diff --git a/homeassistant/components/sofar/switch.py b/homeassistant/components/sofar/switch.py index a33aa6eefe4b..9a41e06b288b 100644 --- a/homeassistant/components/sofar/switch.py +++ b/homeassistant/components/sofar/switch.py @@ -21,6 +21,7 @@ PARALLEL_UPDATES = 1 class SofarSwitchEntityDescription(SwitchEntityDescription, SofarEntityDescription): """Describe a Sofar switch entity.""" + value_fn: Callable[[SofarInverter], RemoteSwitchOnOff | None] write_fn: Callable[[SofarInverter, bool], Awaitable[None]] @@ -29,6 +30,7 @@ SWITCH_DESCRIPTIONS: tuple[SofarSwitchEntityDescription, ...] = ( key="remote_switch_on_off", component="remote", name=None, + value_fn=lambda device: device.remote.remote_switch_on_off, write_fn=lambda device, value: device.remote.write( "remote_switch_on_off", RemoteSwitchOnOff.ON if value else RemoteSwitchOnOff.OFF, @@ -61,8 +63,7 @@ class SofarSwitch(SofarEntity, SwitchEntity): @override def is_on(self) -> bool | None: """Return whether the remote switch is on.""" - component = getattr(self.coordinator.device, self.entity_description.component) - value = getattr(component, self.entity_description.key) + value = self.entity_description.value_fn(self.coordinator.device) return None if value is None else bool(value) async def _async_write(self, value: bool) -> None: diff --git a/tests/components/sofar/test_sensor.py b/tests/components/sofar/test_sensor.py index b9d5e8334f10..5d305a94ed7c 100644 --- a/tests/components/sofar/test_sensor.py +++ b/tests/components/sofar/test_sensor.py @@ -265,6 +265,7 @@ async def test_total_sensor_restore_data_parsing( key="load_consumption_total", component="meter_energy", translation_key="load_consumption_total", + value_fn=lambda device: device.meter_energy.load_consumption_total, ) device.meter_energy.load_consumption_total = None @@ -312,6 +313,10 @@ async def test_total_sensor_seeds_high_water_from_restored_value( component="meter_energy", translation_key="load_consumption_total", state_class=SensorStateClass.TOTAL_INCREASING, + value_fn=lambda device: ( + device.meter_energy.load_consumption_total_corrected.value + ), + total_fn=lambda device: device.meter_energy.load_consumption_total_corrected, ) sensor = SofarTotalSensor(runtime_data, description) sensor.hass = hass @@ -330,6 +335,7 @@ async def test_sensor_dead_link_unavailable(init_integration: MockConfigEntry) - key="grid_frequency", component="grid", translation_key="grid_frequency", + value_fn=lambda device: device.grid.grid_frequency, ) sensor = SofarSensor(runtime_data, description) assert sensor.native_value == 50.0 @@ -347,6 +353,10 @@ async def test_total_sensor_dead_link_unavailable( component="meter_energy", translation_key="load_consumption_total", state_class=SensorStateClass.TOTAL_INCREASING, + value_fn=lambda device: ( + device.meter_energy.load_consumption_total_corrected.value + ), + total_fn=lambda device: device.meter_energy.load_consumption_total_corrected, ) sensor = SofarTotalSensor(runtime_data, description) assert sensor.available @@ -367,6 +377,7 @@ async def test_sensor_availability_on_component_failure( key="grid_frequency", component="grid", translation_key="grid_frequency", + value_fn=lambda device: device.grid.grid_frequency, ) sensor = SofarSensor(runtime_data, description) assert sensor.available @@ -395,6 +406,10 @@ async def test_total_sensor_total_increasing_uses_corrected_value( component="meter_energy", translation_key="load_consumption_total", state_class=SensorStateClass.TOTAL_INCREASING, + value_fn=lambda device: ( + device.meter_energy.load_consumption_total_corrected.value + ), + total_fn=lambda device: device.meter_energy.load_consumption_total_corrected, ) device = runtime_data.readings.device sensor = SofarTotalSensor(runtime_data, description) @@ -404,3 +419,43 @@ async def test_total_sensor_total_increasing_uses_corrected_value( assert sensor.native_value == 42.0 mock_corrected.assert_called_once_with("load_consumption_total") assert sensor.available + + +@pytest.mark.parametrize( + "description", + [ + pytest.param(description, id=description.key) + for description in SENSOR_DESCRIPTIONS + if description.state_class is not SensorStateClass.TOTAL_INCREASING + ], +) +async def test_value_fn_reads_the_field_its_key_names( + init_integration: MockConfigEntry, description: SofarSensorDescription +) -> None: + """Test each value_fn reads the field its key and component name.""" + device = init_integration.runtime_data.readings.device + component = getattr(device, description.component) + sentinel = object() + with patch.dict(component._values, {description.key: sentinel}): + assert description.value_fn(device) is sentinel + + +@pytest.mark.parametrize( + "description", + [ + pytest.param(description, id=description.key) + for description in SENSOR_DESCRIPTIONS + if description.state_class is SensorStateClass.TOTAL_INCREASING + ], +) +async def test_total_reads_and_seeds_its_own_corrected_total( + init_integration: MockConfigEntry, description: SofarSensorDescription +) -> None: + """Test each total reads and seeds the corrected total its key names.""" + device = init_integration.runtime_data.readings.device + component = getattr(device, description.component) + with patch.object(component, "corrected", return_value=42.0) as mock_corrected: + assert description.value_fn(device) == 42.0 + mock_corrected.assert_called_once_with(description.key) + assert description.total_fn is not None + assert description.total_fn(device).name == description.key