mirror of
https://github.com/home-assistant/core.git
synced 2026-09-24 23:41:48 -05:00
Sofar split devices using via devices (#180321)
This commit is contained in:
@@ -9,6 +9,7 @@ from homeassistant.components.modbus import async_get_unit
|
||||
from homeassistant.const import CONF_HOST, CONF_PORT, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryError
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
|
||||
from .const import CONF_UNIT_ID, DOMAIN, SCAN_INTERVAL, SETTINGS_SCAN_INTERVAL
|
||||
from .coordinator import SofarConfigEntry, SofarDataUpdateCoordinator, SofarRuntimeData
|
||||
@@ -59,7 +60,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: SofarConfigEntry) -> boo
|
||||
await readings.async_config_entry_first_refresh()
|
||||
await settings.async_refresh()
|
||||
|
||||
entry.runtime_data = SofarRuntimeData(readings, settings)
|
||||
# Up front: a part's device must name an inverter that has an id.
|
||||
inverter = dr.async_get(hass).async_get_or_create(
|
||||
config_entry_id=entry.entry_id, **readings.device_info
|
||||
)
|
||||
entry.runtime_data = SofarRuntimeData(readings, settings, inverter.id)
|
||||
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
return True
|
||||
|
||||
@@ -150,6 +150,7 @@ class SofarRuntimeData:
|
||||
|
||||
readings: SofarDataUpdateCoordinator
|
||||
settings: SofarDataUpdateCoordinator
|
||||
inverter_device_id: str
|
||||
|
||||
@property
|
||||
def served_components(self) -> frozenset[str]:
|
||||
|
||||
@@ -3,17 +3,23 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import override
|
||||
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
from homeassistant.helpers.entity import EntityDescription
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import SofarDataUpdateCoordinator, SofarRuntimeData
|
||||
|
||||
# A part of the inverter, as (device translation key, number).
|
||||
type SofarPart = tuple[str, int]
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class SofarEntityDescription(EntityDescription):
|
||||
"""Describe a Sofar entity."""
|
||||
|
||||
component: str # attribute name on SofarInverter, e.g. 'grid', 'pv_1_2'
|
||||
part: SofarPart | None = None # None keeps the entity on the inverter
|
||||
|
||||
|
||||
class SofarEntity(CoordinatorEntity[SofarDataUpdateCoordinator]):
|
||||
@@ -33,7 +39,21 @@ class SofarEntity(CoordinatorEntity[SofarDataUpdateCoordinator]):
|
||||
serial = self.coordinator.device.serial_number
|
||||
assert serial is not None
|
||||
self._attr_unique_id = f"{serial}_{entity_description.key}"
|
||||
self._attr_device_info = self.coordinator.device_info
|
||||
self._attr_device_info = self._device_info(runtime_data, serial)
|
||||
|
||||
def _device_info(
|
||||
self, runtime_data: SofarRuntimeData, serial: str
|
||||
) -> dr.DeviceInfo:
|
||||
"""Place the entity on the inverter, or on the part it measures."""
|
||||
if (part := self.entity_description.part) is None:
|
||||
return self.coordinator.device_info
|
||||
kind, number = part
|
||||
return dr.DeviceInfo(
|
||||
identifiers={(DOMAIN, f"{serial}_{kind}_{number}")},
|
||||
via_device_id=runtime_data.inverter_device_id,
|
||||
translation_key=kind,
|
||||
translation_placeholders={"number": str(number)},
|
||||
)
|
||||
|
||||
@property
|
||||
@override
|
||||
|
||||
@@ -16,32 +16,11 @@
|
||||
"bat_config_tempco": {
|
||||
"default": "mdi:battery-check-outline"
|
||||
},
|
||||
"battery_state_of_health_1": {
|
||||
"default": "mdi:battery-heart"
|
||||
},
|
||||
"battery_state_of_health_2": {
|
||||
"default": "mdi:battery-heart"
|
||||
},
|
||||
"battery_state_of_health_3": {
|
||||
"default": "mdi:battery-heart"
|
||||
},
|
||||
"battery_state_of_health_4": {
|
||||
"default": "mdi:battery-heart"
|
||||
},
|
||||
"battery_state_of_health_5": {
|
||||
"default": "mdi:battery-heart"
|
||||
},
|
||||
"battery_state_of_health_6": {
|
||||
"default": "mdi:battery-heart"
|
||||
},
|
||||
"battery_state_of_health_7": {
|
||||
"default": "mdi:battery-heart"
|
||||
},
|
||||
"battery_state_of_health_8": {
|
||||
"default": "mdi:battery-heart"
|
||||
},
|
||||
"battery_state_of_health_total": {
|
||||
"default": "mdi:battery-heart"
|
||||
},
|
||||
"state_of_health": {
|
||||
"default": "mdi:battery-heart"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,9 +63,7 @@ rules:
|
||||
docs-supported-functions: done
|
||||
docs-troubleshooting: done
|
||||
docs-use-cases: todo
|
||||
dynamic-devices:
|
||||
status: exempt
|
||||
comment: One fixed device per config entry; nothing is discovered dynamically at runtime.
|
||||
dynamic-devices: done
|
||||
entity-category: done
|
||||
entity-device-class: done
|
||||
entity-disabled-by-default: done
|
||||
@@ -74,9 +72,7 @@ rules:
|
||||
icon-translations: done
|
||||
reconfiguration-flow: todo
|
||||
repair-issues: todo
|
||||
stale-devices:
|
||||
status: exempt
|
||||
comment: One fixed device per config entry; nothing to detect as stale.
|
||||
stale-devices: todo
|
||||
|
||||
# Platinum
|
||||
async-dependency: done
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
"""Support for Sofar sensors."""
|
||||
|
||||
from collections.abc import Callable
|
||||
from collections.abc import Callable, Mapping
|
||||
from dataclasses import dataclass
|
||||
from datetime import date, datetime, timedelta
|
||||
from enum import IntEnum
|
||||
from typing import cast, override
|
||||
|
||||
from sofar_modbus.modern.device import SofarInverter
|
||||
|
||||
from homeassistant.components.sensor import (
|
||||
RestoreSensor,
|
||||
SensorDeviceClass,
|
||||
@@ -26,7 +28,7 @@ from homeassistant.const import (
|
||||
UnitOfTemperature,
|
||||
UnitOfTime,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.util import dt as dt_util
|
||||
from homeassistant.util.variance import ignore_variance
|
||||
@@ -58,13 +60,53 @@ async def async_setup_entry(
|
||||
"""Set up the Sofar Inverter Modbus sensor platform."""
|
||||
runtime_data = entry.runtime_data
|
||||
served = runtime_data.served_components
|
||||
device = runtime_data.readings.device
|
||||
|
||||
entities: list[SensorEntity] = [
|
||||
async_add_entities(
|
||||
_sensor_class(description)(runtime_data, description)
|
||||
for description in SENSOR_DESCRIPTIONS
|
||||
if description.component in served
|
||||
]
|
||||
async_add_entities(entities)
|
||||
if description.component in served and not _is_battery_pack(description)
|
||||
)
|
||||
|
||||
wired: set[int] = set()
|
||||
|
||||
@callback
|
||||
def _async_add_wired_packs() -> None:
|
||||
"""Add a pack's sensors the first time it reports a voltage."""
|
||||
new = {
|
||||
number
|
||||
for number in _BATTERY_COMPONENTS
|
||||
if number not in wired and _pack_is_wired(device, served, number)
|
||||
}
|
||||
if not new:
|
||||
return
|
||||
wired.update(new)
|
||||
async_add_entities(
|
||||
_sensor_class(description)(runtime_data, description)
|
||||
for description in SENSOR_DESCRIPTIONS
|
||||
if (part := description.part) is not None
|
||||
and part[0] == "battery"
|
||||
and part[1] in new
|
||||
)
|
||||
|
||||
_async_add_wired_packs()
|
||||
entry.async_on_unload(
|
||||
runtime_data.readings.async_add_listener(_async_add_wired_packs)
|
||||
)
|
||||
|
||||
|
||||
def _is_battery_pack(description: SofarSensorDescription) -> bool:
|
||||
"""Whether a description belongs to one numbered battery pack."""
|
||||
return description.part is not None and description.part[0] == "battery"
|
||||
|
||||
|
||||
def _pack_is_wired(device: SofarInverter, served: frozenset[str], number: int) -> bool:
|
||||
"""Whether a pack has answered, so it physically exists."""
|
||||
component_name = _BATTERY_COMPONENTS[number]
|
||||
if component_name not in served:
|
||||
return False
|
||||
component = getattr(device, component_name)
|
||||
return bool(getattr(component, f"battery_voltage_{number}", None))
|
||||
|
||||
|
||||
def _sensor_class(
|
||||
@@ -162,25 +204,142 @@ class SofarSensorDescription(SensorEntityDescription, SofarEntityDescription):
|
||||
"""Describe a Sofar sensor."""
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class _PartMeasurement:
|
||||
"""One measurement every string or pack repeats, before it gets a number."""
|
||||
|
||||
key: str
|
||||
translation_key: str
|
||||
device_class: SensorDeviceClass | None = None
|
||||
native_unit_of_measurement: str | None = None
|
||||
state_class: SensorStateClass | None = None
|
||||
suggested_display_precision: int | None = None
|
||||
entity_category: EntityCategory | None = None
|
||||
entity_registry_enabled_default: bool = True
|
||||
|
||||
|
||||
# Which register block each string or pack is read from.
|
||||
_PV_STRING_COMPONENTS = {
|
||||
1: "pv_1_2",
|
||||
2: "pv_1_2",
|
||||
3: "pv_3",
|
||||
4: "pv_4",
|
||||
5: "pv_5_6",
|
||||
6: "pv_5_6",
|
||||
7: "pv_7_8",
|
||||
8: "pv_7_8",
|
||||
9: "pv_9_10",
|
||||
10: "pv_9_10",
|
||||
}
|
||||
_BATTERY_COMPONENTS = {
|
||||
n: "battery_1_2" if n <= 2 else "battery_3_8" for n in range(1, 9)
|
||||
}
|
||||
|
||||
_PV_STRING_MEASUREMENTS = (
|
||||
_PartMeasurement(
|
||||
key="pv_voltage",
|
||||
translation_key="voltage",
|
||||
device_class=SensorDeviceClass.VOLTAGE,
|
||||
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
_PartMeasurement(
|
||||
key="pv_current",
|
||||
translation_key="current",
|
||||
device_class=SensorDeviceClass.CURRENT,
|
||||
native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
|
||||
suggested_display_precision=2,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
_PartMeasurement(
|
||||
key="pv_power",
|
||||
translation_key="power",
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
native_unit_of_measurement=UnitOfPower.KILO_WATT,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
suggested_display_precision=2,
|
||||
),
|
||||
)
|
||||
|
||||
_BATTERY_MEASUREMENTS = (
|
||||
_PartMeasurement(
|
||||
key="battery_voltage",
|
||||
translation_key="voltage",
|
||||
device_class=SensorDeviceClass.VOLTAGE,
|
||||
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
|
||||
),
|
||||
_PartMeasurement(
|
||||
key="battery_current",
|
||||
translation_key="current",
|
||||
device_class=SensorDeviceClass.CURRENT,
|
||||
native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
|
||||
suggested_display_precision=2,
|
||||
),
|
||||
_PartMeasurement(
|
||||
key="battery_power",
|
||||
translation_key="power",
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
native_unit_of_measurement=UnitOfPower.KILO_WATT,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
suggested_display_precision=2,
|
||||
),
|
||||
_PartMeasurement(
|
||||
key="battery_temperature",
|
||||
translation_key="temperature",
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
),
|
||||
_PartMeasurement(
|
||||
key="battery_capacity",
|
||||
translation_key="state_of_charge",
|
||||
device_class=SensorDeviceClass.BATTERY,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
_PartMeasurement(
|
||||
key="battery_state_of_health",
|
||||
translation_key="state_of_health",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
),
|
||||
_PartMeasurement(
|
||||
key="battery_charge_cycle",
|
||||
translation_key="charge_cycle",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def _part_sensors(
|
||||
kind: str,
|
||||
components: Mapping[int, str],
|
||||
measurements: tuple[_PartMeasurement, ...],
|
||||
) -> tuple[SofarSensorDescription, ...]:
|
||||
"""Repeat a part's measurements across every string or pack it has."""
|
||||
return tuple(
|
||||
SofarSensorDescription(
|
||||
key=f"{measurement.key}_{number}",
|
||||
component=component,
|
||||
translation_key=measurement.translation_key,
|
||||
part=(kind, number),
|
||||
device_class=measurement.device_class,
|
||||
native_unit_of_measurement=measurement.native_unit_of_measurement,
|
||||
state_class=measurement.state_class,
|
||||
suggested_display_precision=measurement.suggested_display_precision,
|
||||
entity_category=measurement.entity_category,
|
||||
entity_registry_enabled_default=(
|
||||
measurement.entity_registry_enabled_default
|
||||
),
|
||||
)
|
||||
for number, component in components.items()
|
||||
for measurement in measurements
|
||||
)
|
||||
|
||||
|
||||
SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = (
|
||||
SofarSensorDescription(
|
||||
key="pv_power_1",
|
||||
component="pv_1_2",
|
||||
translation_key="pv_power_1",
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
native_unit_of_measurement=UnitOfPower.KILO_WATT,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
suggested_display_precision=2,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="pv_power_2",
|
||||
component="pv_1_2",
|
||||
translation_key="pv_power_2",
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
native_unit_of_measurement=UnitOfPower.KILO_WATT,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
suggested_display_precision=2,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="pv_power_total",
|
||||
component="pv_1_2",
|
||||
@@ -1026,737 +1185,6 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = (
|
||||
suggested_display_precision=2,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="pv_voltage_1",
|
||||
component="pv_1_2",
|
||||
translation_key="pv_voltage_1",
|
||||
device_class=SensorDeviceClass.VOLTAGE,
|
||||
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="pv_current_1",
|
||||
component="pv_1_2",
|
||||
translation_key="pv_current_1",
|
||||
device_class=SensorDeviceClass.CURRENT,
|
||||
native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
|
||||
suggested_display_precision=2,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="pv_voltage_2",
|
||||
component="pv_1_2",
|
||||
translation_key="pv_voltage_2",
|
||||
device_class=SensorDeviceClass.VOLTAGE,
|
||||
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="pv_current_2",
|
||||
component="pv_1_2",
|
||||
translation_key="pv_current_2",
|
||||
device_class=SensorDeviceClass.CURRENT,
|
||||
native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
|
||||
suggested_display_precision=2,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="pv_voltage_3",
|
||||
component="pv_3",
|
||||
translation_key="pv_voltage_3",
|
||||
device_class=SensorDeviceClass.VOLTAGE,
|
||||
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="pv_current_3",
|
||||
component="pv_3",
|
||||
translation_key="pv_current_3",
|
||||
device_class=SensorDeviceClass.CURRENT,
|
||||
native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
|
||||
suggested_display_precision=2,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="pv_power_3",
|
||||
component="pv_3",
|
||||
translation_key="pv_power_3",
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
native_unit_of_measurement=UnitOfPower.KILO_WATT,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
suggested_display_precision=2,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="pv_voltage_4",
|
||||
component="pv_4",
|
||||
translation_key="pv_voltage_4",
|
||||
device_class=SensorDeviceClass.VOLTAGE,
|
||||
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="pv_current_4",
|
||||
component="pv_4",
|
||||
translation_key="pv_current_4",
|
||||
device_class=SensorDeviceClass.CURRENT,
|
||||
native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
|
||||
suggested_display_precision=2,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="pv_power_4",
|
||||
component="pv_4",
|
||||
translation_key="pv_power_4",
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
native_unit_of_measurement=UnitOfPower.KILO_WATT,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
suggested_display_precision=2,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="pv_voltage_5",
|
||||
component="pv_5_6",
|
||||
translation_key="pv_voltage_5",
|
||||
device_class=SensorDeviceClass.VOLTAGE,
|
||||
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="pv_current_5",
|
||||
component="pv_5_6",
|
||||
translation_key="pv_current_5",
|
||||
device_class=SensorDeviceClass.CURRENT,
|
||||
native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
|
||||
suggested_display_precision=2,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="pv_power_5",
|
||||
component="pv_5_6",
|
||||
translation_key="pv_power_5",
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
native_unit_of_measurement=UnitOfPower.KILO_WATT,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
suggested_display_precision=2,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="pv_voltage_6",
|
||||
component="pv_5_6",
|
||||
translation_key="pv_voltage_6",
|
||||
device_class=SensorDeviceClass.VOLTAGE,
|
||||
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="pv_current_6",
|
||||
component="pv_5_6",
|
||||
translation_key="pv_current_6",
|
||||
device_class=SensorDeviceClass.CURRENT,
|
||||
native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
|
||||
suggested_display_precision=2,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="pv_power_6",
|
||||
component="pv_5_6",
|
||||
translation_key="pv_power_6",
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
native_unit_of_measurement=UnitOfPower.KILO_WATT,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
suggested_display_precision=2,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="pv_voltage_7",
|
||||
component="pv_7_8",
|
||||
translation_key="pv_voltage_7",
|
||||
device_class=SensorDeviceClass.VOLTAGE,
|
||||
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="pv_current_7",
|
||||
component="pv_7_8",
|
||||
translation_key="pv_current_7",
|
||||
device_class=SensorDeviceClass.CURRENT,
|
||||
native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
|
||||
suggested_display_precision=2,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="pv_power_7",
|
||||
component="pv_7_8",
|
||||
translation_key="pv_power_7",
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
native_unit_of_measurement=UnitOfPower.KILO_WATT,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
suggested_display_precision=2,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="pv_voltage_8",
|
||||
component="pv_7_8",
|
||||
translation_key="pv_voltage_8",
|
||||
device_class=SensorDeviceClass.VOLTAGE,
|
||||
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="pv_current_8",
|
||||
component="pv_7_8",
|
||||
translation_key="pv_current_8",
|
||||
device_class=SensorDeviceClass.CURRENT,
|
||||
native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
|
||||
suggested_display_precision=2,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="pv_power_8",
|
||||
component="pv_7_8",
|
||||
translation_key="pv_power_8",
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
native_unit_of_measurement=UnitOfPower.KILO_WATT,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
suggested_display_precision=2,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="pv_voltage_9",
|
||||
component="pv_9_10",
|
||||
translation_key="pv_voltage_9",
|
||||
device_class=SensorDeviceClass.VOLTAGE,
|
||||
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="pv_current_9",
|
||||
component="pv_9_10",
|
||||
translation_key="pv_current_9",
|
||||
device_class=SensorDeviceClass.CURRENT,
|
||||
native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
|
||||
suggested_display_precision=2,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="pv_power_9",
|
||||
component="pv_9_10",
|
||||
translation_key="pv_power_9",
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
native_unit_of_measurement=UnitOfPower.KILO_WATT,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
suggested_display_precision=2,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="pv_voltage_10",
|
||||
component="pv_9_10",
|
||||
translation_key="pv_voltage_10",
|
||||
device_class=SensorDeviceClass.VOLTAGE,
|
||||
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="pv_current_10",
|
||||
component="pv_9_10",
|
||||
translation_key="pv_current_10",
|
||||
device_class=SensorDeviceClass.CURRENT,
|
||||
native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
|
||||
suggested_display_precision=2,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="pv_power_10",
|
||||
component="pv_9_10",
|
||||
translation_key="pv_power_10",
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
native_unit_of_measurement=UnitOfPower.KILO_WATT,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
suggested_display_precision=2,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="battery_voltage_1",
|
||||
component="battery_1_2",
|
||||
translation_key="battery_voltage_1",
|
||||
device_class=SensorDeviceClass.VOLTAGE,
|
||||
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="battery_current_1",
|
||||
component="battery_1_2",
|
||||
translation_key="battery_current_1",
|
||||
device_class=SensorDeviceClass.CURRENT,
|
||||
native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
|
||||
suggested_display_precision=2,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="battery_power_1",
|
||||
component="battery_1_2",
|
||||
translation_key="battery_power_1",
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
native_unit_of_measurement=UnitOfPower.KILO_WATT,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
suggested_display_precision=2,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="battery_temperature_1",
|
||||
component="battery_1_2",
|
||||
translation_key="battery_temperature_1",
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="battery_capacity_1",
|
||||
component="battery_1_2",
|
||||
translation_key="battery_state_of_charge_1",
|
||||
device_class=SensorDeviceClass.BATTERY,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="battery_state_of_health_1",
|
||||
component="battery_1_2",
|
||||
translation_key="battery_state_of_health_1",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="battery_charge_cycle_1",
|
||||
component="battery_1_2",
|
||||
translation_key="battery_charge_cycle_1",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="battery_voltage_2",
|
||||
component="battery_1_2",
|
||||
translation_key="battery_voltage_2",
|
||||
device_class=SensorDeviceClass.VOLTAGE,
|
||||
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="battery_current_2",
|
||||
component="battery_1_2",
|
||||
translation_key="battery_current_2",
|
||||
device_class=SensorDeviceClass.CURRENT,
|
||||
native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
|
||||
entity_registry_enabled_default=False,
|
||||
suggested_display_precision=2,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="battery_power_2",
|
||||
component="battery_1_2",
|
||||
translation_key="battery_power_2",
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
native_unit_of_measurement=UnitOfPower.KILO_WATT,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
entity_registry_enabled_default=False,
|
||||
suggested_display_precision=2,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="battery_temperature_2",
|
||||
component="battery_1_2",
|
||||
translation_key="battery_temperature_2",
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="battery_capacity_2",
|
||||
component="battery_1_2",
|
||||
translation_key="battery_state_of_charge_2",
|
||||
device_class=SensorDeviceClass.BATTERY,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="battery_state_of_health_2",
|
||||
component="battery_1_2",
|
||||
translation_key="battery_state_of_health_2",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="battery_charge_cycle_2",
|
||||
component="battery_1_2",
|
||||
translation_key="battery_charge_cycle_2",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="battery_voltage_3",
|
||||
component="battery_3_8",
|
||||
translation_key="battery_voltage_3",
|
||||
device_class=SensorDeviceClass.VOLTAGE,
|
||||
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="battery_current_3",
|
||||
component="battery_3_8",
|
||||
translation_key="battery_current_3",
|
||||
device_class=SensorDeviceClass.CURRENT,
|
||||
native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
|
||||
entity_registry_enabled_default=False,
|
||||
suggested_display_precision=2,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="battery_power_3",
|
||||
component="battery_3_8",
|
||||
translation_key="battery_power_3",
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
native_unit_of_measurement=UnitOfPower.KILO_WATT,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
entity_registry_enabled_default=False,
|
||||
suggested_display_precision=2,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="battery_temperature_3",
|
||||
component="battery_3_8",
|
||||
translation_key="battery_temperature_3",
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="battery_capacity_3",
|
||||
component="battery_3_8",
|
||||
translation_key="battery_state_of_charge_3",
|
||||
device_class=SensorDeviceClass.BATTERY,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="battery_state_of_health_3",
|
||||
component="battery_3_8",
|
||||
translation_key="battery_state_of_health_3",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="battery_charge_cycle_3",
|
||||
component="battery_3_8",
|
||||
translation_key="battery_charge_cycle_3",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="battery_voltage_4",
|
||||
component="battery_3_8",
|
||||
translation_key="battery_voltage_4",
|
||||
device_class=SensorDeviceClass.VOLTAGE,
|
||||
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="battery_current_4",
|
||||
component="battery_3_8",
|
||||
translation_key="battery_current_4",
|
||||
device_class=SensorDeviceClass.CURRENT,
|
||||
native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
|
||||
entity_registry_enabled_default=False,
|
||||
suggested_display_precision=2,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="battery_power_4",
|
||||
component="battery_3_8",
|
||||
translation_key="battery_power_4",
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
native_unit_of_measurement=UnitOfPower.KILO_WATT,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
entity_registry_enabled_default=False,
|
||||
suggested_display_precision=2,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="battery_temperature_4",
|
||||
component="battery_3_8",
|
||||
translation_key="battery_temperature_4",
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="battery_capacity_4",
|
||||
component="battery_3_8",
|
||||
translation_key="battery_state_of_charge_4",
|
||||
device_class=SensorDeviceClass.BATTERY,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="battery_state_of_health_4",
|
||||
component="battery_3_8",
|
||||
translation_key="battery_state_of_health_4",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="battery_charge_cycle_4",
|
||||
component="battery_3_8",
|
||||
translation_key="battery_charge_cycle_4",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="battery_voltage_5",
|
||||
component="battery_3_8",
|
||||
translation_key="battery_voltage_5",
|
||||
device_class=SensorDeviceClass.VOLTAGE,
|
||||
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="battery_current_5",
|
||||
component="battery_3_8",
|
||||
translation_key="battery_current_5",
|
||||
device_class=SensorDeviceClass.CURRENT,
|
||||
native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
|
||||
entity_registry_enabled_default=False,
|
||||
suggested_display_precision=2,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="battery_power_5",
|
||||
component="battery_3_8",
|
||||
translation_key="battery_power_5",
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
native_unit_of_measurement=UnitOfPower.KILO_WATT,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
entity_registry_enabled_default=False,
|
||||
suggested_display_precision=2,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="battery_temperature_5",
|
||||
component="battery_3_8",
|
||||
translation_key="battery_temperature_5",
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="battery_capacity_5",
|
||||
component="battery_3_8",
|
||||
translation_key="battery_state_of_charge_5",
|
||||
device_class=SensorDeviceClass.BATTERY,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="battery_state_of_health_5",
|
||||
component="battery_3_8",
|
||||
translation_key="battery_state_of_health_5",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="battery_charge_cycle_5",
|
||||
component="battery_3_8",
|
||||
translation_key="battery_charge_cycle_5",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="battery_voltage_6",
|
||||
component="battery_3_8",
|
||||
translation_key="battery_voltage_6",
|
||||
device_class=SensorDeviceClass.VOLTAGE,
|
||||
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="battery_current_6",
|
||||
component="battery_3_8",
|
||||
translation_key="battery_current_6",
|
||||
device_class=SensorDeviceClass.CURRENT,
|
||||
native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
|
||||
entity_registry_enabled_default=False,
|
||||
suggested_display_precision=2,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="battery_power_6",
|
||||
component="battery_3_8",
|
||||
translation_key="battery_power_6",
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
native_unit_of_measurement=UnitOfPower.KILO_WATT,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
entity_registry_enabled_default=False,
|
||||
suggested_display_precision=2,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="battery_temperature_6",
|
||||
component="battery_3_8",
|
||||
translation_key="battery_temperature_6",
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="battery_capacity_6",
|
||||
component="battery_3_8",
|
||||
translation_key="battery_state_of_charge_6",
|
||||
device_class=SensorDeviceClass.BATTERY,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="battery_state_of_health_6",
|
||||
component="battery_3_8",
|
||||
translation_key="battery_state_of_health_6",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="battery_charge_cycle_6",
|
||||
component="battery_3_8",
|
||||
translation_key="battery_charge_cycle_6",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="battery_voltage_7",
|
||||
component="battery_3_8",
|
||||
translation_key="battery_voltage_7",
|
||||
device_class=SensorDeviceClass.VOLTAGE,
|
||||
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="battery_current_7",
|
||||
component="battery_3_8",
|
||||
translation_key="battery_current_7",
|
||||
device_class=SensorDeviceClass.CURRENT,
|
||||
native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
|
||||
entity_registry_enabled_default=False,
|
||||
suggested_display_precision=2,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="battery_power_7",
|
||||
component="battery_3_8",
|
||||
translation_key="battery_power_7",
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
native_unit_of_measurement=UnitOfPower.KILO_WATT,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
entity_registry_enabled_default=False,
|
||||
suggested_display_precision=2,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="battery_temperature_7",
|
||||
component="battery_3_8",
|
||||
translation_key="battery_temperature_7",
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="battery_capacity_7",
|
||||
component="battery_3_8",
|
||||
translation_key="battery_state_of_charge_7",
|
||||
device_class=SensorDeviceClass.BATTERY,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="battery_state_of_health_7",
|
||||
component="battery_3_8",
|
||||
translation_key="battery_state_of_health_7",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="battery_charge_cycle_7",
|
||||
component="battery_3_8",
|
||||
translation_key="battery_charge_cycle_7",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="battery_voltage_8",
|
||||
component="battery_3_8",
|
||||
translation_key="battery_voltage_8",
|
||||
device_class=SensorDeviceClass.VOLTAGE,
|
||||
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="battery_current_8",
|
||||
component="battery_3_8",
|
||||
translation_key="battery_current_8",
|
||||
device_class=SensorDeviceClass.CURRENT,
|
||||
native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
|
||||
entity_registry_enabled_default=False,
|
||||
suggested_display_precision=2,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="battery_power_8",
|
||||
component="battery_3_8",
|
||||
translation_key="battery_power_8",
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
native_unit_of_measurement=UnitOfPower.KILO_WATT,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
entity_registry_enabled_default=False,
|
||||
suggested_display_precision=2,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="battery_temperature_8",
|
||||
component="battery_3_8",
|
||||
translation_key="battery_temperature_8",
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="battery_capacity_8",
|
||||
component="battery_3_8",
|
||||
translation_key="battery_state_of_charge_8",
|
||||
device_class=SensorDeviceClass.BATTERY,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="battery_state_of_health_8",
|
||||
component="battery_3_8",
|
||||
translation_key="battery_state_of_health_8",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="battery_charge_cycle_8",
|
||||
component="battery_3_8",
|
||||
translation_key="battery_charge_cycle_8",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
SofarSensorDescription(
|
||||
key="battery_power_total",
|
||||
component="battery_totals",
|
||||
@@ -2067,3 +1495,7 @@ SENSOR_DESCRIPTIONS: tuple[SofarSensorDescription, ...] = (
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
),
|
||||
)
|
||||
|
||||
SENSOR_DESCRIPTIONS += _part_sensors(
|
||||
"pv_string", _PV_STRING_COMPONENTS, _PV_STRING_MEASUREMENTS
|
||||
) + _part_sensors("battery", _BATTERY_COMPONENTS, _BATTERY_MEASUREMENTS)
|
||||
|
||||
@@ -23,6 +23,14 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"device": {
|
||||
"battery": {
|
||||
"name": "Battery {number}"
|
||||
},
|
||||
"pv_string": {
|
||||
"name": "PV string {number}"
|
||||
}
|
||||
},
|
||||
"entity": {
|
||||
"sensor": {
|
||||
"active_power_load_sys": {
|
||||
@@ -148,54 +156,6 @@
|
||||
"bat_config_voltage_float": {
|
||||
"name": "Battery config lead acid float voltage"
|
||||
},
|
||||
"battery_charge_cycle_1": {
|
||||
"name": "Battery charge cycle 1"
|
||||
},
|
||||
"battery_charge_cycle_2": {
|
||||
"name": "Battery charge cycle 2"
|
||||
},
|
||||
"battery_charge_cycle_3": {
|
||||
"name": "Battery charge cycle 3"
|
||||
},
|
||||
"battery_charge_cycle_4": {
|
||||
"name": "Battery charge cycle 4"
|
||||
},
|
||||
"battery_charge_cycle_5": {
|
||||
"name": "Battery charge cycle 5"
|
||||
},
|
||||
"battery_charge_cycle_6": {
|
||||
"name": "Battery charge cycle 6"
|
||||
},
|
||||
"battery_charge_cycle_7": {
|
||||
"name": "Battery charge cycle 7"
|
||||
},
|
||||
"battery_charge_cycle_8": {
|
||||
"name": "Battery charge cycle 8"
|
||||
},
|
||||
"battery_current_1": {
|
||||
"name": "Battery current 1"
|
||||
},
|
||||
"battery_current_2": {
|
||||
"name": "Battery current 2"
|
||||
},
|
||||
"battery_current_3": {
|
||||
"name": "Battery current 3"
|
||||
},
|
||||
"battery_current_4": {
|
||||
"name": "Battery current 4"
|
||||
},
|
||||
"battery_current_5": {
|
||||
"name": "Battery current 5"
|
||||
},
|
||||
"battery_current_6": {
|
||||
"name": "Battery current 6"
|
||||
},
|
||||
"battery_current_7": {
|
||||
"name": "Battery current 7"
|
||||
},
|
||||
"battery_current_8": {
|
||||
"name": "Battery current 8"
|
||||
},
|
||||
"battery_input_energy_today": {
|
||||
"name": "Battery input energy today"
|
||||
},
|
||||
@@ -208,134 +168,20 @@
|
||||
"battery_output_energy_total": {
|
||||
"name": "Battery output energy total"
|
||||
},
|
||||
"battery_power_1": {
|
||||
"name": "Battery power 1"
|
||||
},
|
||||
"battery_power_2": {
|
||||
"name": "Battery power 2"
|
||||
},
|
||||
"battery_power_3": {
|
||||
"name": "Battery power 3"
|
||||
},
|
||||
"battery_power_4": {
|
||||
"name": "Battery power 4"
|
||||
},
|
||||
"battery_power_5": {
|
||||
"name": "Battery power 5"
|
||||
},
|
||||
"battery_power_6": {
|
||||
"name": "Battery power 6"
|
||||
},
|
||||
"battery_power_7": {
|
||||
"name": "Battery power 7"
|
||||
},
|
||||
"battery_power_8": {
|
||||
"name": "Battery power 8"
|
||||
},
|
||||
"battery_power_total": {
|
||||
"name": "Battery power total"
|
||||
},
|
||||
"battery_state_of_charge_1": {
|
||||
"name": "Battery state of charge 1"
|
||||
},
|
||||
"battery_state_of_charge_2": {
|
||||
"name": "Battery state of charge 2"
|
||||
},
|
||||
"battery_state_of_charge_3": {
|
||||
"name": "Battery state of charge 3"
|
||||
},
|
||||
"battery_state_of_charge_4": {
|
||||
"name": "Battery state of charge 4"
|
||||
},
|
||||
"battery_state_of_charge_5": {
|
||||
"name": "Battery state of charge 5"
|
||||
},
|
||||
"battery_state_of_charge_6": {
|
||||
"name": "Battery state of charge 6"
|
||||
},
|
||||
"battery_state_of_charge_7": {
|
||||
"name": "Battery state of charge 7"
|
||||
},
|
||||
"battery_state_of_charge_8": {
|
||||
"name": "Battery state of charge 8"
|
||||
},
|
||||
"battery_state_of_charge_total": {
|
||||
"name": "Battery state of charge total"
|
||||
},
|
||||
"battery_state_of_health_1": {
|
||||
"name": "Battery state of health 1"
|
||||
},
|
||||
"battery_state_of_health_2": {
|
||||
"name": "Battery state of health 2"
|
||||
},
|
||||
"battery_state_of_health_3": {
|
||||
"name": "Battery state of health 3"
|
||||
},
|
||||
"battery_state_of_health_4": {
|
||||
"name": "Battery state of health 4"
|
||||
},
|
||||
"battery_state_of_health_5": {
|
||||
"name": "Battery state of health 5"
|
||||
},
|
||||
"battery_state_of_health_6": {
|
||||
"name": "Battery state of health 6"
|
||||
},
|
||||
"battery_state_of_health_7": {
|
||||
"name": "Battery state of health 7"
|
||||
},
|
||||
"battery_state_of_health_8": {
|
||||
"name": "Battery state of health 8"
|
||||
},
|
||||
"battery_state_of_health_total": {
|
||||
"name": "Battery state of health total"
|
||||
},
|
||||
"battery_temperature_1": {
|
||||
"name": "Battery temperature 1"
|
||||
"charge_cycle": {
|
||||
"name": "Charge cycle"
|
||||
},
|
||||
"battery_temperature_2": {
|
||||
"name": "Battery temperature 2"
|
||||
},
|
||||
"battery_temperature_3": {
|
||||
"name": "Battery temperature 3"
|
||||
},
|
||||
"battery_temperature_4": {
|
||||
"name": "Battery temperature 4"
|
||||
},
|
||||
"battery_temperature_5": {
|
||||
"name": "Battery temperature 5"
|
||||
},
|
||||
"battery_temperature_6": {
|
||||
"name": "Battery temperature 6"
|
||||
},
|
||||
"battery_temperature_7": {
|
||||
"name": "Battery temperature 7"
|
||||
},
|
||||
"battery_temperature_8": {
|
||||
"name": "Battery temperature 8"
|
||||
},
|
||||
"battery_voltage_1": {
|
||||
"name": "Battery voltage 1"
|
||||
},
|
||||
"battery_voltage_2": {
|
||||
"name": "Battery voltage 2"
|
||||
},
|
||||
"battery_voltage_3": {
|
||||
"name": "Battery voltage 3"
|
||||
},
|
||||
"battery_voltage_4": {
|
||||
"name": "Battery voltage 4"
|
||||
},
|
||||
"battery_voltage_5": {
|
||||
"name": "Battery voltage 5"
|
||||
},
|
||||
"battery_voltage_6": {
|
||||
"name": "Battery voltage 6"
|
||||
},
|
||||
"battery_voltage_7": {
|
||||
"name": "Battery voltage 7"
|
||||
},
|
||||
"battery_voltage_8": {
|
||||
"name": "Battery voltage 8"
|
||||
"current": {
|
||||
"name": "Current"
|
||||
},
|
||||
"current_output_l1": {
|
||||
"name": "Current output L1"
|
||||
@@ -502,6 +348,9 @@
|
||||
"passive_eps_wait_time": {
|
||||
"name": "EPS wait time"
|
||||
},
|
||||
"power": {
|
||||
"name": "Power"
|
||||
},
|
||||
"power_factor_output_l1": {
|
||||
"name": "Power factor output L1"
|
||||
},
|
||||
@@ -520,99 +369,9 @@
|
||||
"power_factor_pcc_l3": {
|
||||
"name": "Power factor PCC L3"
|
||||
},
|
||||
"pv_current_1": {
|
||||
"name": "PV current 1"
|
||||
},
|
||||
"pv_current_10": {
|
||||
"name": "PV current 10"
|
||||
},
|
||||
"pv_current_2": {
|
||||
"name": "PV current 2"
|
||||
},
|
||||
"pv_current_3": {
|
||||
"name": "PV current 3"
|
||||
},
|
||||
"pv_current_4": {
|
||||
"name": "PV current 4"
|
||||
},
|
||||
"pv_current_5": {
|
||||
"name": "PV current 5"
|
||||
},
|
||||
"pv_current_6": {
|
||||
"name": "PV current 6"
|
||||
},
|
||||
"pv_current_7": {
|
||||
"name": "PV current 7"
|
||||
},
|
||||
"pv_current_8": {
|
||||
"name": "PV current 8"
|
||||
},
|
||||
"pv_current_9": {
|
||||
"name": "PV current 9"
|
||||
},
|
||||
"pv_power_1": {
|
||||
"name": "PV power 1"
|
||||
},
|
||||
"pv_power_10": {
|
||||
"name": "PV power 10"
|
||||
},
|
||||
"pv_power_2": {
|
||||
"name": "PV power 2"
|
||||
},
|
||||
"pv_power_3": {
|
||||
"name": "PV power 3"
|
||||
},
|
||||
"pv_power_4": {
|
||||
"name": "PV power 4"
|
||||
},
|
||||
"pv_power_5": {
|
||||
"name": "PV power 5"
|
||||
},
|
||||
"pv_power_6": {
|
||||
"name": "PV power 6"
|
||||
},
|
||||
"pv_power_7": {
|
||||
"name": "PV power 7"
|
||||
},
|
||||
"pv_power_8": {
|
||||
"name": "PV power 8"
|
||||
},
|
||||
"pv_power_9": {
|
||||
"name": "PV power 9"
|
||||
},
|
||||
"pv_power_total": {
|
||||
"name": "PV power total"
|
||||
},
|
||||
"pv_voltage_1": {
|
||||
"name": "PV voltage 1"
|
||||
},
|
||||
"pv_voltage_10": {
|
||||
"name": "PV voltage 10"
|
||||
},
|
||||
"pv_voltage_2": {
|
||||
"name": "PV voltage 2"
|
||||
},
|
||||
"pv_voltage_3": {
|
||||
"name": "PV voltage 3"
|
||||
},
|
||||
"pv_voltage_4": {
|
||||
"name": "PV voltage 4"
|
||||
},
|
||||
"pv_voltage_5": {
|
||||
"name": "PV voltage 5"
|
||||
},
|
||||
"pv_voltage_6": {
|
||||
"name": "PV voltage 6"
|
||||
},
|
||||
"pv_voltage_7": {
|
||||
"name": "PV voltage 7"
|
||||
},
|
||||
"pv_voltage_8": {
|
||||
"name": "PV voltage 8"
|
||||
},
|
||||
"pv_voltage_9": {
|
||||
"name": "PV voltage 9"
|
||||
},
|
||||
"reactive_power_offgrid_total": {
|
||||
"name": "Reactive power off-grid total"
|
||||
},
|
||||
@@ -649,6 +408,12 @@
|
||||
"solar_generation_total": {
|
||||
"name": "Solar generation total"
|
||||
},
|
||||
"state_of_charge": {
|
||||
"name": "State of charge"
|
||||
},
|
||||
"state_of_health": {
|
||||
"name": "State of health"
|
||||
},
|
||||
"sync_rtc_result": {
|
||||
"name": "Update system time operation result",
|
||||
"state": {
|
||||
@@ -676,6 +441,12 @@
|
||||
"waiting": "Waiting"
|
||||
}
|
||||
},
|
||||
"temperature": {
|
||||
"name": "Temperature"
|
||||
},
|
||||
"voltage": {
|
||||
"name": "Voltage"
|
||||
},
|
||||
"voltage_l1": {
|
||||
"name": "Voltage L1"
|
||||
},
|
||||
|
||||
@@ -53,5 +53,9 @@ def seed_pv_inverter(unit: MockModbusUnit, serial: str = MOCK_SERIAL) -> None:
|
||||
def seed_hybrid_inverter(
|
||||
unit: MockModbusUnit, serial: str = MOCK_HYBRID_SERIAL
|
||||
) -> None:
|
||||
"""Seed identity registers for a hybrid inverter; the rest read as zero."""
|
||||
"""Seed a hybrid inverter with battery packs 1 and 3 wired, 2 absent."""
|
||||
_seed_common(unit, serial)
|
||||
unit.holding[0x0604] = 520 # battery_voltage_1 -> 52.0 V
|
||||
unit.holding[0x0608] = 87 # battery_capacity_1 -> 87%
|
||||
unit.holding[0x0612] = 515 # battery_voltage_3 -> 51.5 V
|
||||
unit.holding[0x0616] = 85 # battery_capacity_3 -> 85%
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -20,11 +20,20 @@ from homeassistant.const import STATE_UNAVAILABLE
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||
|
||||
from . import MOCK_HW_VERSION, MOCK_SERIAL, MOCK_SW_VERSION, MOCK_USER_INPUT
|
||||
from . import (
|
||||
MOCK_HW_VERSION,
|
||||
MOCK_HYBRID_MODEL,
|
||||
MOCK_HYBRID_SERIAL,
|
||||
MOCK_SERIAL,
|
||||
MOCK_SW_VERSION,
|
||||
MOCK_USER_INPUT,
|
||||
seed_hybrid_inverter,
|
||||
)
|
||||
|
||||
from tests.common import MockConfigEntry, async_fire_time_changed
|
||||
|
||||
PV_POWER_REGISTER = 0x0586
|
||||
BATTERY_3_VOLTAGE_REGISTER = 0x0612
|
||||
SOLAR_GENERATION_REGISTER = 0x0684
|
||||
|
||||
|
||||
@@ -336,3 +345,139 @@ async def test_link_dying_during_the_retry_marks_sensors_unavailable(
|
||||
)
|
||||
assert entity_id is not None
|
||||
assert hass.states.get(entity_id).state == STATE_UNAVAILABLE
|
||||
|
||||
|
||||
async def test_pv_strings_become_their_own_devices(
|
||||
hass: HomeAssistant,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
init_integration: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test every PV string the inverter serves gets a device of its own."""
|
||||
inverter = device_registry.async_get_device_by_identifier(
|
||||
(DOMAIN, MOCK_SERIAL), init_integration.entry_id
|
||||
)
|
||||
assert inverter is not None
|
||||
|
||||
strings = [
|
||||
device
|
||||
for device in dr.async_entries_for_config_entry(
|
||||
device_registry, init_integration.entry_id
|
||||
)
|
||||
if device.via_device_id == inverter.id
|
||||
]
|
||||
|
||||
# Two MPPTs on this model, so strings 3 to 10 are not served at all.
|
||||
assert {device.name for device in strings} == {"PV string 1", "PV string 2"}
|
||||
|
||||
|
||||
async def test_pv_aggregate_stays_on_the_inverter(
|
||||
hass: HomeAssistant,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
entity_registry: er.EntityRegistry,
|
||||
init_integration: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test a total living in a per-string component is not moved off."""
|
||||
inverter = device_registry.async_get_device_by_identifier(
|
||||
(DOMAIN, MOCK_SERIAL), init_integration.entry_id
|
||||
)
|
||||
assert inverter is not None
|
||||
|
||||
# pv_power_total sits in the pv_1_2 component but measures all strings.
|
||||
entity_id = entity_registry.async_get_entity_id(
|
||||
SENSOR_DOMAIN, DOMAIN, f"{MOCK_SERIAL}_pv_power_total"
|
||||
)
|
||||
assert entity_id is not None
|
||||
assert entity_registry.async_get(entity_id).device_id == inverter.id
|
||||
|
||||
|
||||
async def test_only_wired_battery_packs_become_devices(
|
||||
hass: HomeAssistant,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
entity_registry: er.EntityRegistry,
|
||||
) -> None:
|
||||
"""Test packs are counted by what answers, not by the register map."""
|
||||
connection = MockModbusConnection()
|
||||
seed_hybrid_inverter(connection.for_unit(1))
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
unique_id=MOCK_HYBRID_SERIAL,
|
||||
data=MOCK_USER_INPUT,
|
||||
title=MOCK_HYBRID_MODEL,
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
with patch(
|
||||
"homeassistant.components.sofar.async_get_unit",
|
||||
side_effect=lambda hass, entry, params, unit_id: connection.for_unit(unit_id),
|
||||
):
|
||||
await hass.config_entries.async_setup(entry.entry_id)
|
||||
await hass.async_block_till_done(wait_background_tasks=True)
|
||||
|
||||
inverter = device_registry.async_get_device_by_identifier(
|
||||
(DOMAIN, MOCK_HYBRID_SERIAL), entry.entry_id
|
||||
)
|
||||
assert inverter is not None
|
||||
battery_names = {
|
||||
device.name
|
||||
for device in dr.async_entries_for_config_entry(device_registry, entry.entry_id)
|
||||
if device.via_device_id == inverter.id and device.name.startswith("Battery")
|
||||
}
|
||||
|
||||
# The seed wires packs 1 and 3; the map allows 8, so the rest must not.
|
||||
assert battery_names == {"Battery 1", "Battery 3"}
|
||||
assert (
|
||||
entity_registry.async_get_entity_id(
|
||||
SENSOR_DOMAIN, DOMAIN, f"{MOCK_HYBRID_SERIAL}_battery_voltage_2"
|
||||
)
|
||||
is None
|
||||
)
|
||||
|
||||
inverter = device_registry.async_get_device_by_identifier(
|
||||
(DOMAIN, MOCK_HYBRID_SERIAL), entry.entry_id
|
||||
)
|
||||
assert inverter is not None
|
||||
# A combined total is the inverter's, not any one pack's.
|
||||
total_id = entity_registry.async_get_entity_id(
|
||||
SENSOR_DOMAIN, DOMAIN, f"{MOCK_HYBRID_SERIAL}_battery_capacity_total"
|
||||
)
|
||||
assert total_id is not None
|
||||
assert entity_registry.async_get(total_id).device_id == inverter.id
|
||||
|
||||
|
||||
async def test_battery_pack_appears_once_its_block_answers(
|
||||
hass: HomeAssistant,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
entity_registry: er.EntityRegistry,
|
||||
) -> None:
|
||||
"""Test a pack whose block failed at setup is added when it recovers."""
|
||||
connection = MockModbusConnection()
|
||||
unit = connection.for_unit(1)
|
||||
seed_hybrid_inverter(unit)
|
||||
# Inside the battery_3_8 block, so pack 3 cannot be seen at setup.
|
||||
unit.fail_read(BATTERY_3_VOLTAGE_REGISTER, ModbusError("block busy"))
|
||||
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
unique_id=MOCK_HYBRID_SERIAL,
|
||||
data=MOCK_USER_INPUT,
|
||||
title=MOCK_HYBRID_MODEL,
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
with patch(
|
||||
"homeassistant.components.sofar.async_get_unit",
|
||||
side_effect=lambda hass, entry, params, unit_id: connection.for_unit(unit_id),
|
||||
):
|
||||
await hass.config_entries.async_setup(entry.entry_id)
|
||||
await hass.async_block_till_done(wait_background_tasks=True)
|
||||
|
||||
unique_id = f"{MOCK_HYBRID_SERIAL}_battery_voltage_3"
|
||||
assert entity_registry.async_get_entity_id(SENSOR_DOMAIN, DOMAIN, unique_id) is None
|
||||
|
||||
unit.fail_read(BATTERY_3_VOLTAGE_REGISTER, None)
|
||||
freezer.tick(timedelta(seconds=SCAN_INTERVAL))
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# No reload: the coordinator's own listener notices the pack answering.
|
||||
entity_id = entity_registry.async_get_entity_id(SENSOR_DOMAIN, DOMAIN, unique_id)
|
||||
assert entity_id is not None
|
||||
assert hass.states.get(entity_id).state == "51.5"
|
||||
|
||||
@@ -103,8 +103,8 @@ async def test_sensor_entities_created_and_state(
|
||||
MOCK_HYBRID_SERIAL,
|
||||
MOCK_HYBRID_MODEL,
|
||||
seed_hybrid_inverter,
|
||||
181,
|
||||
39,
|
||||
139,
|
||||
46,
|
||||
id="hybrid",
|
||||
),
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user