Bump solarlog_cli to 0.2.2 (#124948)

* Add inverter-devices

* Minor code adjustments

* Update manifest.json

Seperate dependency upgrade to seperate PR

* Update requirements_all.txt

Seperate dependency upgrade to seperate PR

* Update requirements_test_all.txt

Seperate dependency upgrade to seperate PR

* Update homeassistant/components/solarlog/sensor.py

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>

* Split up base class, document SolarLogSensorEntityDescription

* Split up sensor types

* Update snapshot

* Bump solarlog_cli to 0.2.1

* Add strict typing

* Bump fyta_cli to 0.6.3 (#124574)

* Ensure write access to hassrelease data folder (#124573)

Co-authored-by: Robert Resch <robert@resch.dev>

* Update a roborock blocking call to be fully async (#124266)

Remove a blocking call in roborock

* Add inverter-devices

* Split up sensor types

* Update snapshot

* Bump solarlog_cli to 0.2.1

* Backport/rebase

* Tidy up

* Simplyfication coordinator.py

* Minor adjustments

* Ruff

* Bump solarlog_cli to 0.2.2

* Update homeassistant/components/solarlog/sensor.py

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>

* Update homeassistant/components/solarlog/config_flow.py

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>

* Update homeassistant/components/solarlog/sensor.py

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>

* Update persentage-values in fixture

---------

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
Co-authored-by: Robert Resch <robert@resch.dev>
Co-authored-by: Allen Porter <allen@thebends.org>
This commit is contained in:
dontinelli 2024-09-01 12:47:52 +02:00 committed by GitHub
parent 68162e1a27
commit 1661304f10
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 350 additions and 125 deletions

View file

@ -411,6 +411,7 @@ homeassistant.components.slack.*
homeassistant.components.sleepiq.* homeassistant.components.sleepiq.*
homeassistant.components.smhi.* homeassistant.components.smhi.*
homeassistant.components.snooz.* homeassistant.components.snooz.*
homeassistant.components.solarlog.*
homeassistant.components.sonarr.* homeassistant.components.sonarr.*
homeassistant.components.speedtestdotnet.* homeassistant.components.speedtestdotnet.*
homeassistant.components.sql.* homeassistant.components.sql.*

View file

@ -19,7 +19,7 @@ _LOGGER = logging.getLogger(__name__)
@callback @callback
def solarlog_entries(hass: HomeAssistant): def solarlog_entries(hass: HomeAssistant) -> set[str]:
"""Return the hosts already configured.""" """Return the hosts already configured."""
return { return {
entry.data[CONF_HOST] for entry in hass.config_entries.async_entries(DOMAIN) entry.data[CONF_HOST] for entry in hass.config_entries.async_entries(DOMAIN)
@ -36,7 +36,7 @@ class SolarLogConfigFlow(ConfigFlow, domain=DOMAIN):
"""Initialize the config flow.""" """Initialize the config flow."""
self._errors: dict = {} self._errors: dict = {}
def _host_in_configuration_exists(self, host) -> bool: def _host_in_configuration_exists(self, host: str) -> bool:
"""Return True if host exists in configuration.""" """Return True if host exists in configuration."""
if host in solarlog_entries(self.hass): if host in solarlog_entries(self.hass):
return True return True
@ -50,7 +50,7 @@ class SolarLogConfigFlow(ConfigFlow, domain=DOMAIN):
url = ParseResult("http", netloc, path, *url[3:]) url = ParseResult("http", netloc, path, *url[3:])
return url.geturl() return url.geturl()
async def _test_connection(self, host): async def _test_connection(self, host: str) -> bool:
"""Check if we can connect to the Solar-Log device.""" """Check if we can connect to the Solar-Log device."""
solarlog = SolarLogConnector(host) solarlog = SolarLogConnector(host)
try: try:
@ -66,11 +66,12 @@ class SolarLogConfigFlow(ConfigFlow, domain=DOMAIN):
return True return True
async def async_step_user(self, user_input=None) -> ConfigFlowResult: async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Step when user initializes a integration.""" """Step when user initializes a integration."""
self._errors = {} self._errors = {}
if user_input is not None: if user_input is not None:
# set some defaults in case we need to return to the form
user_input[CONF_NAME] = slugify(user_input[CONF_NAME]) user_input[CONF_NAME] = slugify(user_input[CONF_NAME])
user_input[CONF_HOST] = self._parse_url(user_input[CONF_HOST]) user_input[CONF_HOST] = self._parse_url(user_input[CONF_HOST])
@ -81,20 +82,14 @@ class SolarLogConfigFlow(ConfigFlow, domain=DOMAIN):
title=user_input[CONF_NAME], data=user_input title=user_input[CONF_NAME], data=user_input
) )
else: else:
user_input = {} user_input = {CONF_NAME: DEFAULT_NAME, CONF_HOST: DEFAULT_HOST}
user_input[CONF_NAME] = DEFAULT_NAME
user_input[CONF_HOST] = DEFAULT_HOST
return self.async_show_form( return self.async_show_form(
step_id="user", step_id="user",
data_schema=vol.Schema( data_schema=vol.Schema(
{ {
vol.Required( vol.Required(CONF_NAME, default=user_input[CONF_NAME]): str,
CONF_NAME, default=user_input.get(CONF_NAME, DEFAULT_NAME) vol.Required(CONF_HOST, default=user_input[CONF_HOST]): str,
): str,
vol.Required(
CONF_HOST, default=user_input.get(CONF_HOST, DEFAULT_HOST)
): str,
vol.Required("extended_data", default=False): bool, vol.Required("extended_data", default=False): bool,
} }
), ),

View file

@ -12,11 +12,12 @@ from solarlog_cli.solarlog_exceptions import (
SolarLogConnectionError, SolarLogConnectionError,
SolarLogUpdateError, SolarLogUpdateError,
) )
from solarlog_cli.solarlog_models import SolarlogData
from homeassistant.const import CONF_HOST from homeassistant.const import CONF_HOST
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import update_coordinator from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -24,7 +25,7 @@ if TYPE_CHECKING:
from . import SolarlogConfigEntry from . import SolarlogConfigEntry
class SolarLogCoordinator(update_coordinator.DataUpdateCoordinator): class SolarLogCoordinator(DataUpdateCoordinator[SolarlogData]):
"""Get and update the latest data.""" """Get and update the latest data."""
def __init__(self, hass: HomeAssistant, entry: SolarlogConfigEntry) -> None: def __init__(self, hass: HomeAssistant, entry: SolarlogConfigEntry) -> None:
@ -43,29 +44,29 @@ class SolarLogCoordinator(update_coordinator.DataUpdateCoordinator):
self.name = entry.title self.name = entry.title
self.host = url.geturl() self.host = url.geturl()
extended_data = entry.data["extended_data"]
self.solarlog = SolarLogConnector( self.solarlog = SolarLogConnector(
self.host, extended_data, hass.config.time_zone self.host, entry.data["extended_data"], hass.config.time_zone
) )
async def _async_setup(self) -> None: async def _async_setup(self) -> None:
"""Do initialization logic.""" """Do initialization logic."""
if self.solarlog.extended_data: if self.solarlog.extended_data:
device_list = await self.solarlog.client.get_device_list() device_list = await self.solarlog.update_device_list()
self.solarlog.set_enabled_devices({key: True for key in device_list}) self.solarlog.set_enabled_devices({key: True for key in device_list})
async def _async_update_data(self): async def _async_update_data(self) -> SolarlogData:
"""Update the data from the SolarLog device.""" """Update the data from the SolarLog device."""
_LOGGER.debug("Start data update") _LOGGER.debug("Start data update")
try: try:
data = await self.solarlog.update_data() data = await self.solarlog.update_data()
await self.solarlog.update_device_list() if self.solarlog.extended_data:
await self.solarlog.update_device_list()
data.inverter_data = await self.solarlog.update_inverter_data()
except SolarLogConnectionError as err: except SolarLogConnectionError as err:
raise ConfigEntryNotReady(err) from err raise ConfigEntryNotReady(err) from err
except SolarLogUpdateError as err: except SolarLogUpdateError as err:
raise update_coordinator.UpdateFailed(err) from err raise UpdateFailed(err) from err
_LOGGER.debug("Data successfully updated") _LOGGER.debug("Data successfully updated")

View file

@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/solarlog", "documentation": "https://www.home-assistant.io/integrations/solarlog",
"iot_class": "local_polling", "iot_class": "local_polling",
"loggers": ["solarlog_cli"], "loggers": ["solarlog_cli"],
"requirements": ["solarlog_cli==0.1.6"] "requirements": ["solarlog_cli==0.2.2"]
} }

View file

@ -5,7 +5,8 @@ from __future__ import annotations
from collections.abc import Callable from collections.abc import Callable
from dataclasses import dataclass from dataclasses import dataclass
from datetime import datetime from datetime import datetime
from typing import Any
from solarlog_cli.solarlog_models import InverterData, SolarlogData
from homeassistant.components.sensor import ( from homeassistant.components.sensor import (
SensorDeviceClass, SensorDeviceClass,
@ -21,200 +22,219 @@ from homeassistant.const import (
) )
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import StateType
from . import SolarlogConfigEntry from . import SolarlogConfigEntry
from .entity import SolarLogCoordinatorEntity, SolarLogInverterEntity from .entity import SolarLogCoordinatorEntity, SolarLogInverterEntity
@dataclass(frozen=True) @dataclass(frozen=True, kw_only=True)
class SolarLogSensorEntityDescription(SensorEntityDescription): class SolarLogCoordinatorSensorEntityDescription(SensorEntityDescription):
"""Describes Solarlog sensor entity.""" """Describes Solarlog coordinator sensor entity."""
value_fn: Callable[[float | int], float] | Callable[[datetime], datetime] = ( value_fn: Callable[[SolarlogData], StateType | datetime | None]
lambda value: value
)
SOLARLOG_SENSOR_TYPES: tuple[SolarLogSensorEntityDescription, ...] = ( @dataclass(frozen=True, kw_only=True)
SolarLogSensorEntityDescription( class SolarLogInverterSensorEntityDescription(SensorEntityDescription):
"""Describes Solarlog inverter sensor entity."""
value_fn: Callable[[InverterData], float | None]
SOLARLOG_SENSOR_TYPES: tuple[SolarLogCoordinatorSensorEntityDescription, ...] = (
SolarLogCoordinatorSensorEntityDescription(
key="last_updated", key="last_updated",
translation_key="last_update", translation_key="last_update",
device_class=SensorDeviceClass.TIMESTAMP, device_class=SensorDeviceClass.TIMESTAMP,
value_fn=lambda data: data.last_updated,
), ),
SolarLogSensorEntityDescription( SolarLogCoordinatorSensorEntityDescription(
key="power_ac", key="power_ac",
translation_key="power_ac", translation_key="power_ac",
native_unit_of_measurement=UnitOfPower.WATT, native_unit_of_measurement=UnitOfPower.WATT,
device_class=SensorDeviceClass.POWER, device_class=SensorDeviceClass.POWER,
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda data: data.power_ac,
), ),
SolarLogSensorEntityDescription( SolarLogCoordinatorSensorEntityDescription(
key="power_dc", key="power_dc",
translation_key="power_dc", translation_key="power_dc",
native_unit_of_measurement=UnitOfPower.WATT, native_unit_of_measurement=UnitOfPower.WATT,
device_class=SensorDeviceClass.POWER, device_class=SensorDeviceClass.POWER,
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda data: data.power_dc,
), ),
SolarLogSensorEntityDescription( SolarLogCoordinatorSensorEntityDescription(
key="voltage_ac", key="voltage_ac",
translation_key="voltage_ac", translation_key="voltage_ac",
native_unit_of_measurement=UnitOfElectricPotential.VOLT, native_unit_of_measurement=UnitOfElectricPotential.VOLT,
device_class=SensorDeviceClass.VOLTAGE, device_class=SensorDeviceClass.VOLTAGE,
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda data: data.voltage_ac,
), ),
SolarLogSensorEntityDescription( SolarLogCoordinatorSensorEntityDescription(
key="voltage_dc", key="voltage_dc",
translation_key="voltage_dc", translation_key="voltage_dc",
native_unit_of_measurement=UnitOfElectricPotential.VOLT, native_unit_of_measurement=UnitOfElectricPotential.VOLT,
device_class=SensorDeviceClass.VOLTAGE, device_class=SensorDeviceClass.VOLTAGE,
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda data: data.voltage_dc,
), ),
SolarLogSensorEntityDescription( SolarLogCoordinatorSensorEntityDescription(
key="yield_day", key="yield_day",
translation_key="yield_day", translation_key="yield_day",
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
device_class=SensorDeviceClass.ENERGY, device_class=SensorDeviceClass.ENERGY,
value_fn=lambda value: round(value / 1000, 3), value_fn=lambda data: round(data.yield_day / 1000, 3),
), ),
SolarLogSensorEntityDescription( SolarLogCoordinatorSensorEntityDescription(
key="yield_yesterday", key="yield_yesterday",
translation_key="yield_yesterday", translation_key="yield_yesterday",
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
device_class=SensorDeviceClass.ENERGY, device_class=SensorDeviceClass.ENERGY,
value_fn=lambda value: round(value / 1000, 3), value_fn=lambda data: round(data.yield_yesterday / 1000, 3),
), ),
SolarLogSensorEntityDescription( SolarLogCoordinatorSensorEntityDescription(
key="yield_month", key="yield_month",
translation_key="yield_month", translation_key="yield_month",
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
device_class=SensorDeviceClass.ENERGY, device_class=SensorDeviceClass.ENERGY,
value_fn=lambda value: round(value / 1000, 3), value_fn=lambda data: round(data.yield_month / 1000, 3),
), ),
SolarLogSensorEntityDescription( SolarLogCoordinatorSensorEntityDescription(
key="yield_year", key="yield_year",
translation_key="yield_year", translation_key="yield_year",
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
device_class=SensorDeviceClass.ENERGY, device_class=SensorDeviceClass.ENERGY,
value_fn=lambda value: round(value / 1000, 3), value_fn=lambda data: round(data.yield_year / 1000, 3),
), ),
SolarLogSensorEntityDescription( SolarLogCoordinatorSensorEntityDescription(
key="yield_total", key="yield_total",
translation_key="yield_total", translation_key="yield_total",
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
device_class=SensorDeviceClass.ENERGY, device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL, state_class=SensorStateClass.TOTAL,
value_fn=lambda value: round(value / 1000, 3), value_fn=lambda data: round(data.yield_total / 1000, 3),
), ),
SolarLogSensorEntityDescription( SolarLogCoordinatorSensorEntityDescription(
key="consumption_ac", key="consumption_ac",
translation_key="consumption_ac", translation_key="consumption_ac",
native_unit_of_measurement=UnitOfPower.WATT, native_unit_of_measurement=UnitOfPower.WATT,
device_class=SensorDeviceClass.POWER, device_class=SensorDeviceClass.POWER,
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda data: data.consumption_ac,
), ),
SolarLogSensorEntityDescription( SolarLogCoordinatorSensorEntityDescription(
key="consumption_day", key="consumption_day",
translation_key="consumption_day", translation_key="consumption_day",
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
device_class=SensorDeviceClass.ENERGY, device_class=SensorDeviceClass.ENERGY,
value_fn=lambda value: round(value / 1000, 3), value_fn=lambda data: round(data.consumption_day / 1000, 3),
), ),
SolarLogSensorEntityDescription( SolarLogCoordinatorSensorEntityDescription(
key="consumption_yesterday", key="consumption_yesterday",
translation_key="consumption_yesterday", translation_key="consumption_yesterday",
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
device_class=SensorDeviceClass.ENERGY, device_class=SensorDeviceClass.ENERGY,
value_fn=lambda value: round(value / 1000, 3), value_fn=lambda data: round(data.consumption_yesterday / 1000, 3),
), ),
SolarLogSensorEntityDescription( SolarLogCoordinatorSensorEntityDescription(
key="consumption_month", key="consumption_month",
translation_key="consumption_month", translation_key="consumption_month",
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
device_class=SensorDeviceClass.ENERGY, device_class=SensorDeviceClass.ENERGY,
value_fn=lambda value: round(value / 1000, 3), value_fn=lambda data: round(data.consumption_month / 1000, 3),
), ),
SolarLogSensorEntityDescription( SolarLogCoordinatorSensorEntityDescription(
key="consumption_year", key="consumption_year",
translation_key="consumption_year", translation_key="consumption_year",
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
device_class=SensorDeviceClass.ENERGY, device_class=SensorDeviceClass.ENERGY,
value_fn=lambda value: round(value / 1000, 3), value_fn=lambda data: round(data.consumption_year / 1000, 3),
), ),
SolarLogSensorEntityDescription( SolarLogCoordinatorSensorEntityDescription(
key="consumption_total", key="consumption_total",
translation_key="consumption_total", translation_key="consumption_total",
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
device_class=SensorDeviceClass.ENERGY, device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL, state_class=SensorStateClass.TOTAL,
value_fn=lambda value: round(value / 1000, 3), value_fn=lambda data: round(data.consumption_total / 1000, 3),
), ),
SolarLogSensorEntityDescription( SolarLogCoordinatorSensorEntityDescription(
key="self_consumption_year", key="self_consumption_year",
translation_key="self_consumption_year", translation_key="self_consumption_year",
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
device_class=SensorDeviceClass.ENERGY, device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL_INCREASING, state_class=SensorStateClass.TOTAL_INCREASING,
value_fn=lambda data: data.self_consumption_year,
), ),
SolarLogSensorEntityDescription( SolarLogCoordinatorSensorEntityDescription(
key="total_power", key="total_power",
translation_key="total_power", translation_key="total_power",
native_unit_of_measurement=UnitOfPower.WATT, native_unit_of_measurement=UnitOfPower.WATT,
device_class=SensorDeviceClass.POWER, device_class=SensorDeviceClass.POWER,
value_fn=lambda data: data.total_power,
), ),
SolarLogSensorEntityDescription( SolarLogCoordinatorSensorEntityDescription(
key="alternator_loss", key="alternator_loss",
translation_key="alternator_loss", translation_key="alternator_loss",
native_unit_of_measurement=UnitOfPower.WATT, native_unit_of_measurement=UnitOfPower.WATT,
device_class=SensorDeviceClass.POWER, device_class=SensorDeviceClass.POWER,
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda data: data.alternator_loss,
), ),
SolarLogSensorEntityDescription( SolarLogCoordinatorSensorEntityDescription(
key="capacity", key="capacity",
translation_key="capacity", translation_key="capacity",
native_unit_of_measurement=PERCENTAGE, native_unit_of_measurement=PERCENTAGE,
device_class=SensorDeviceClass.POWER_FACTOR, device_class=SensorDeviceClass.POWER_FACTOR,
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda value: round(value * 100, 1), value_fn=lambda data: data.capacity,
), ),
SolarLogSensorEntityDescription( SolarLogCoordinatorSensorEntityDescription(
key="efficiency", key="efficiency",
translation_key="efficiency", translation_key="efficiency",
native_unit_of_measurement=PERCENTAGE, native_unit_of_measurement=PERCENTAGE,
device_class=SensorDeviceClass.POWER_FACTOR, device_class=SensorDeviceClass.POWER_FACTOR,
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda value: round(value * 100, 1), value_fn=lambda data: data.efficiency,
), ),
SolarLogSensorEntityDescription( SolarLogCoordinatorSensorEntityDescription(
key="power_available", key="power_available",
translation_key="power_available", translation_key="power_available",
native_unit_of_measurement=UnitOfPower.WATT, native_unit_of_measurement=UnitOfPower.WATT,
device_class=SensorDeviceClass.POWER, device_class=SensorDeviceClass.POWER,
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda data: data.power_available,
), ),
SolarLogSensorEntityDescription( SolarLogCoordinatorSensorEntityDescription(
key="usage", key="usage",
translation_key="usage", translation_key="usage",
native_unit_of_measurement=PERCENTAGE, native_unit_of_measurement=PERCENTAGE,
device_class=SensorDeviceClass.POWER_FACTOR, device_class=SensorDeviceClass.POWER_FACTOR,
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda value: round(value * 100, 1), value_fn=lambda data: data.usage,
), ),
) )
INVERTER_SENSOR_TYPES: tuple[SolarLogSensorEntityDescription, ...] = ( INVERTER_SENSOR_TYPES: tuple[SolarLogInverterSensorEntityDescription, ...] = (
SolarLogSensorEntityDescription( SolarLogInverterSensorEntityDescription(
key="current_power", key="current_power",
translation_key="current_power", translation_key="current_power",
native_unit_of_measurement=UnitOfPower.WATT, native_unit_of_measurement=UnitOfPower.WATT,
device_class=SensorDeviceClass.POWER, device_class=SensorDeviceClass.POWER,
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda inverter: inverter.current_power,
), ),
SolarLogSensorEntityDescription( SolarLogInverterSensorEntityDescription(
key="consumption_year", key="consumption_year",
translation_key="consumption_year", translation_key="consumption_year",
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
device_class=SensorDeviceClass.ENERGY, device_class=SensorDeviceClass.ENERGY,
value_fn=lambda value: round(value / 1000, 3), value_fn=lambda inverter: None
if inverter.consumption_year is None
else round(inverter.consumption_year / 1000, 3),
), ),
) )
@ -227,21 +247,18 @@ async def async_setup_entry(
"""Add solarlog entry.""" """Add solarlog entry."""
coordinator = entry.runtime_data coordinator = entry.runtime_data
# https://github.com/python/mypy/issues/14294
entities: list[SensorEntity] = [ entities: list[SensorEntity] = [
SolarLogCoordinatorSensor(coordinator, sensor) SolarLogCoordinatorSensor(coordinator, sensor)
for sensor in SOLARLOG_SENSOR_TYPES for sensor in SOLARLOG_SENSOR_TYPES
] ]
device_data: dict[str, Any] = coordinator.data["devices"] device_data = coordinator.data.inverter_data
if not device_data: if device_data:
entities.extend( entities.extend(
SolarLogInverterSensor(coordinator, sensor, int(device_id)) SolarLogInverterSensor(coordinator, sensor, device_id)
for device_id in device_data for device_id in device_data
for sensor in INVERTER_SENSOR_TYPES for sensor in INVERTER_SENSOR_TYPES
if sensor.key in device_data[device_id]
) )
async_add_entities(entities) async_add_entities(entities)
@ -250,26 +267,24 @@ async def async_setup_entry(
class SolarLogCoordinatorSensor(SolarLogCoordinatorEntity, SensorEntity): class SolarLogCoordinatorSensor(SolarLogCoordinatorEntity, SensorEntity):
"""Represents a SolarLog sensor.""" """Represents a SolarLog sensor."""
entity_description: SolarLogSensorEntityDescription entity_description: SolarLogCoordinatorSensorEntityDescription
@property @property
def native_value(self) -> float | datetime: def native_value(self) -> StateType | datetime:
"""Return the state for this sensor.""" """Return the state for this sensor."""
val = self.coordinator.data[self.entity_description.key] return self.entity_description.value_fn(self.coordinator.data)
return self.entity_description.value_fn(val)
class SolarLogInverterSensor(SolarLogInverterEntity, SensorEntity): class SolarLogInverterSensor(SolarLogInverterEntity, SensorEntity):
"""Represents a SolarLog inverter sensor.""" """Represents a SolarLog inverter sensor."""
entity_description: SolarLogSensorEntityDescription entity_description: SolarLogInverterSensorEntityDescription
@property @property
def native_value(self) -> float | datetime: def native_value(self) -> StateType:
"""Return the state for this sensor.""" """Return the state for this sensor."""
val = self.coordinator.data["devices"][self.device_id][ return self.entity_description.value_fn(
self.entity_description.key self.coordinator.data.inverter_data[self.device_id]
] )
return self.entity_description.value_fn(val)

View file

@ -3866,6 +3866,16 @@ disallow_untyped_defs = true
warn_return_any = true warn_return_any = true
warn_unreachable = true warn_unreachable = true
[mypy-homeassistant.components.solarlog.*]
check_untyped_defs = true
disallow_incomplete_defs = true
disallow_subclassing_any = true
disallow_untyped_calls = true
disallow_untyped_decorators = true
disallow_untyped_defs = true
warn_return_any = true
warn_unreachable = true
[mypy-homeassistant.components.sonarr.*] [mypy-homeassistant.components.sonarr.*]
check_untyped_defs = true check_untyped_defs = true
disallow_incomplete_defs = true disallow_incomplete_defs = true

View file

@ -2650,7 +2650,7 @@ soco==0.30.4
solaredge-local==0.2.3 solaredge-local==0.2.3
# homeassistant.components.solarlog # homeassistant.components.solarlog
solarlog_cli==0.1.6 solarlog_cli==0.2.2
# homeassistant.components.solax # homeassistant.components.solax
solax==3.1.1 solax==3.1.1

View file

@ -2093,7 +2093,7 @@ snapcast==2.3.6
soco==0.30.4 soco==0.30.4
# homeassistant.components.solarlog # homeassistant.components.solarlog
solarlog_cli==0.1.6 solarlog_cli==0.2.2
# homeassistant.components.solax # homeassistant.components.solax
solax==3.1.1 solax==3.1.1

View file

@ -17,3 +17,5 @@ async def setup_platform(
with patch("homeassistant.components.solarlog.PLATFORMS", platforms): with patch("homeassistant.components.solarlog.PLATFORMS", platforms):
await hass.config_entries.async_setup(config_entry.entry_id) await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done() await hass.async_block_till_done()
return config_entry

View file

@ -1,10 +1,10 @@
"""Test helpers.""" """Test helpers."""
from collections.abc import Generator from collections.abc import Generator
from datetime import UTC, datetime
from unittest.mock import AsyncMock, patch from unittest.mock import AsyncMock, patch
import pytest import pytest
from solarlog_cli.solarlog_models import InverterData, SolarlogData
from homeassistant.components.solarlog.const import DOMAIN as SOLARLOG_DOMAIN from homeassistant.components.solarlog.const import DOMAIN as SOLARLOG_DOMAIN
from homeassistant.const import CONF_HOST, CONF_NAME from homeassistant.const import CONF_HOST, CONF_NAME
@ -13,6 +13,19 @@ from .const import HOST, NAME
from tests.common import MockConfigEntry, load_json_object_fixture from tests.common import MockConfigEntry, load_json_object_fixture
DEVICE_LIST = {
0: InverterData(name="Inverter 1", enabled=True),
1: InverterData(name="Inverter 2", enabled=True),
}
INVERTER_DATA = {
0: InverterData(
name="Inverter 1", enabled=True, consumption_year=354687, current_power=5
),
1: InverterData(
name="Inverter 2", enabled=True, consumption_year=354, current_power=6
),
}
@pytest.fixture @pytest.fixture
def mock_config_entry() -> MockConfigEntry: def mock_config_entry() -> MockConfigEntry:
@ -34,28 +47,18 @@ def mock_config_entry() -> MockConfigEntry:
def mock_solarlog_connector(): def mock_solarlog_connector():
"""Build a fixture for the SolarLog API that connects successfully and returns one device.""" """Build a fixture for the SolarLog API that connects successfully and returns one device."""
data = SolarlogData.from_dict(
load_json_object_fixture("solarlog_data.json", SOLARLOG_DOMAIN)
)
data.inverter_data = INVERTER_DATA
mock_solarlog_api = AsyncMock() mock_solarlog_api = AsyncMock()
mock_solarlog_api.test_connection = AsyncMock(return_value=True) mock_solarlog_api.test_connection.return_value = True
data = {
"devices": {
0: {"consumption_total": 354687, "current_power": 5},
}
}
data |= load_json_object_fixture("solarlog_data.json", SOLARLOG_DOMAIN)
data["last_updated"] = datetime.fromisoformat(data["last_updated"]).astimezone(UTC)
mock_solarlog_api.update_data.return_value = data mock_solarlog_api.update_data.return_value = data
mock_solarlog_api.device_list.return_value = { mock_solarlog_api.update_device_list.return_value = INVERTER_DATA
0: {"name": "Inverter 1"}, mock_solarlog_api.update_inverter_data.return_value = INVERTER_DATA
1: {"name": "Inverter 2"},
}
mock_solarlog_api.device_name = {0: "Inverter 1", 1: "Inverter 2"}.get mock_solarlog_api.device_name = {0: "Inverter 1", 1: "Inverter 2"}.get
mock_solarlog_api.client.get_device_list.return_value = { mock_solarlog_api.device_enabled = {0: True, 1: False}.get
0: {"name": "Inverter 1"},
1: {"name": "Inverter 2"},
}
mock_solarlog_api.client.close = AsyncMock(return_value=None)
with ( with (
patch( patch(

View file

@ -17,9 +17,9 @@
"total_power": 120, "total_power": 120,
"self_consumption_year": 545, "self_consumption_year": 545,
"alternator_loss": 2, "alternator_loss": 2,
"efficiency": 0.9804, "efficiency": 98.1,
"usage": 0.5487, "usage": 54.8,
"power_available": 45.13, "power_available": 45.13,
"capacity": 0.85, "capacity": 85.5,
"last_updated": "2024-08-01T15:20:45" "last_updated": "2024-08-01T15:20:45Z"
} }

View file

@ -1,4 +1,103 @@
# serializer version: 1 # serializer version: 1
# name: test_all_entities[sensor.inverter_1_consumption_total-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.TOTAL: 'total'>,
}),
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': None,
'entity_id': 'sensor.inverter_1_consumption_total',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <SensorDeviceClass.ENERGY: 'energy'>,
'original_icon': None,
'original_name': 'Consumption total',
'platform': 'solarlog',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'consumption_total',
'unique_id': 'ce5f5431554d101905d31797e1232da8-inverter_1-consumption_total',
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
})
# ---
# name: test_all_entities[sensor.inverter_1_consumption_total-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'energy',
'friendly_name': 'Inverter 1 Consumption total',
'state_class': <SensorStateClass.TOTAL: 'total'>,
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
}),
'context': <ANY>,
'entity_id': 'sensor.inverter_1_consumption_total',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '354.687',
})
# ---
# name: test_all_entities[sensor.inverter_1_consumption_year-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': None,
'entity_id': 'sensor.inverter_1_consumption_year',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <SensorDeviceClass.ENERGY: 'energy'>,
'original_icon': None,
'original_name': 'Consumption year',
'platform': 'solarlog',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'consumption_year',
'unique_id': 'ce5f5431554d101905d31797e1232da8-inverter_1-consumption_year',
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
})
# ---
# name: test_all_entities[sensor.inverter_1_consumption_year-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'energy',
'friendly_name': 'Inverter 1 Consumption year',
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
}),
'context': <ANY>,
'entity_id': 'sensor.inverter_1_consumption_year',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '354.687',
})
# ---
# name: test_all_entities[sensor.inverter_1_power-entry] # name: test_all_entities[sensor.inverter_1_power-entry]
EntityRegistryEntrySnapshot({ EntityRegistryEntrySnapshot({
'aliases': set({ 'aliases': set({
@ -50,6 +149,105 @@
'state': '5', 'state': '5',
}) })
# --- # ---
# name: test_all_entities[sensor.inverter_2_consumption_year-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': None,
'entity_id': 'sensor.inverter_2_consumption_year',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <SensorDeviceClass.ENERGY: 'energy'>,
'original_icon': None,
'original_name': 'Consumption year',
'platform': 'solarlog',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'consumption_year',
'unique_id': 'ce5f5431554d101905d31797e1232da8-inverter_2-consumption_year',
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
})
# ---
# name: test_all_entities[sensor.inverter_2_consumption_year-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'energy',
'friendly_name': 'Inverter 2 Consumption year',
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
}),
'context': <ANY>,
'entity_id': 'sensor.inverter_2_consumption_year',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '0.354',
})
# ---
# name: test_all_entities[sensor.inverter_2_power-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': None,
'entity_id': 'sensor.inverter_2_power',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <SensorDeviceClass.POWER: 'power'>,
'original_icon': None,
'original_name': 'Power',
'platform': 'solarlog',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'current_power',
'unique_id': 'ce5f5431554d101905d31797e1232da8-inverter_2-current_power',
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
})
# ---
# name: test_all_entities[sensor.inverter_2_power-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'power',
'friendly_name': 'Inverter 2 Power',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
}),
'context': <ANY>,
'entity_id': 'sensor.inverter_2_power',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '6',
})
# ---
# name: test_all_entities[sensor.solarlog_alternator_loss-entry] # name: test_all_entities[sensor.solarlog_alternator_loss-entry]
EntityRegistryEntrySnapshot({ EntityRegistryEntrySnapshot({
'aliases': set({ 'aliases': set({
@ -98,7 +296,7 @@
'last_changed': <ANY>, 'last_changed': <ANY>,
'last_reported': <ANY>, 'last_reported': <ANY>,
'last_updated': <ANY>, 'last_updated': <ANY>,
'state': '2', 'state': '2.0',
}) })
# --- # ---
# name: test_all_entities[sensor.solarlog_capacity-entry] # name: test_all_entities[sensor.solarlog_capacity-entry]
@ -149,7 +347,7 @@
'last_changed': <ANY>, 'last_changed': <ANY>,
'last_reported': <ANY>, 'last_reported': <ANY>,
'last_updated': <ANY>, 'last_updated': <ANY>,
'state': '85.0', 'state': '85.5',
}) })
# --- # ---
# name: test_all_entities[sensor.solarlog_consumption_ac-entry] # name: test_all_entities[sensor.solarlog_consumption_ac-entry]
@ -494,7 +692,7 @@
'last_changed': <ANY>, 'last_changed': <ANY>,
'last_reported': <ANY>, 'last_reported': <ANY>,
'last_updated': <ANY>, 'last_updated': <ANY>,
'state': '98.0', 'state': '98.1',
}) })
# --- # ---
# name: test_all_entities[sensor.solarlog_installed_peak_power-entry] # name: test_all_entities[sensor.solarlog_installed_peak_power-entry]
@ -542,7 +740,7 @@
'last_changed': <ANY>, 'last_changed': <ANY>,
'last_reported': <ANY>, 'last_reported': <ANY>,
'last_updated': <ANY>, 'last_updated': <ANY>,
'state': '120', 'state': '120.0',
}) })
# --- # ---
# name: test_all_entities[sensor.solarlog_last_update-entry] # name: test_all_entities[sensor.solarlog_last_update-entry]
@ -640,7 +838,7 @@
'last_changed': <ANY>, 'last_changed': <ANY>,
'last_reported': <ANY>, 'last_reported': <ANY>,
'last_updated': <ANY>, 'last_updated': <ANY>,
'state': '100', 'state': '100.0',
}) })
# --- # ---
# name: test_all_entities[sensor.solarlog_power_available-entry] # name: test_all_entities[sensor.solarlog_power_available-entry]
@ -742,7 +940,7 @@
'last_changed': <ANY>, 'last_changed': <ANY>,
'last_reported': <ANY>, 'last_reported': <ANY>,
'last_updated': <ANY>, 'last_updated': <ANY>,
'state': '102', 'state': '102.0',
}) })
# --- # ---
# name: test_all_entities[sensor.solarlog_self_consumption_year-entry] # name: test_all_entities[sensor.solarlog_self_consumption_year-entry]
@ -793,7 +991,7 @@
'last_changed': <ANY>, 'last_changed': <ANY>,
'last_reported': <ANY>, 'last_reported': <ANY>,
'last_updated': <ANY>, 'last_updated': <ANY>,
'state': '545', 'state': '545.0',
}) })
# --- # ---
# name: test_all_entities[sensor.solarlog_usage-entry] # name: test_all_entities[sensor.solarlog_usage-entry]
@ -844,7 +1042,7 @@
'last_changed': <ANY>, 'last_changed': <ANY>,
'last_reported': <ANY>, 'last_reported': <ANY>,
'last_updated': <ANY>, 'last_updated': <ANY>,
'state': '54.9', 'state': '54.8',
}) })
# --- # ---
# name: test_all_entities[sensor.solarlog_voltage_ac-entry] # name: test_all_entities[sensor.solarlog_voltage_ac-entry]
@ -895,7 +1093,7 @@
'last_changed': <ANY>, 'last_changed': <ANY>,
'last_reported': <ANY>, 'last_reported': <ANY>,
'last_updated': <ANY>, 'last_updated': <ANY>,
'state': '100', 'state': '100.0',
}) })
# --- # ---
# name: test_all_entities[sensor.solarlog_voltage_dc-entry] # name: test_all_entities[sensor.solarlog_voltage_dc-entry]
@ -946,7 +1144,7 @@
'last_changed': <ANY>, 'last_changed': <ANY>,
'last_reported': <ANY>, 'last_reported': <ANY>,
'last_updated': <ANY>, 'last_updated': <ANY>,
'state': '100', 'state': '100.0',
}) })
# --- # ---
# name: test_all_entities[sensor.solarlog_yield_day-entry] # name: test_all_entities[sensor.solarlog_yield_day-entry]

View file

@ -123,7 +123,7 @@ async def test_form_exceptions(
assert result["data"]["extended_data"] is False assert result["data"]["extended_data"] is False
async def test_abort_if_already_setup(hass: HomeAssistant, test_connect) -> None: async def test_abort_if_already_setup(hass: HomeAssistant, test_connect: None) -> None:
"""Test we abort if the device is already setup.""" """Test we abort if the device is already setup."""
flow = init_config_flow(hass) flow = init_config_flow(hass)
MockConfigEntry( MockConfigEntry(