Add Landis+Gyr poll on restart (#89644)

This commit is contained in:
Vincent Knoop Pathuis 2023-03-18 20:50:50 +01:00 committed by GitHub
parent d106cb48d2
commit 6ad9f420ab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 87 additions and 167 deletions

View File

@ -29,6 +29,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
await coordinator.async_config_entry_first_refresh()
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True

View File

@ -1,14 +1,16 @@
"""Platform for sensor integration."""
from __future__ import annotations
from dataclasses import asdict
from collections.abc import Callable
from dataclasses import dataclass
from datetime import datetime
import logging
from ultraheat_api.response import HeatMeterResponse
from homeassistant.components.sensor import (
RestoreSensor,
SensorDeviceClass,
SensorEntity,
SensorEntityDescription,
SensorStateClass,
)
@ -25,6 +27,7 @@ from homeassistant.const import (
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import StateType
from homeassistant.helpers.update_coordinator import (
CoordinatorEntity,
DataUpdateCoordinator,
@ -36,177 +39,220 @@ from . import DOMAIN
_LOGGER = logging.getLogger(__name__)
@dataclass
class HeatMeterSensorEntityDescriptionMixin:
"""Mixin for additional Heat Meter sensor description attributes ."""
value_fn: Callable[[HeatMeterResponse], StateType | datetime]
@dataclass
class HeatMeterSensorEntityDescription(
SensorEntityDescription, HeatMeterSensorEntityDescriptionMixin
):
"""Heat Meter sensor description."""
HEAT_METER_SENSOR_TYPES = (
SensorEntityDescription(
HeatMeterSensorEntityDescription(
key="volume_usage_m3",
icon="mdi:fire",
name="Volume usage",
device_class=SensorDeviceClass.VOLUME,
native_unit_of_measurement=UnitOfVolume.CUBIC_METERS,
state_class=SensorStateClass.TOTAL,
value_fn=lambda res: getattr(res, "volume_usage_m3", None),
),
SensorEntityDescription(
HeatMeterSensorEntityDescription(
key="heat_usage_gj",
icon="mdi:fire",
name="Heat usage GJ",
native_unit_of_measurement=UnitOfEnergy.GIGA_JOULE,
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL,
value_fn=lambda res: getattr(res, "heat_usage_gj", None),
),
SensorEntityDescription(
HeatMeterSensorEntityDescription(
key="heat_previous_year_gj",
icon="mdi:fire",
name="Heat previous year GJ",
native_unit_of_measurement="GJ",
native_unit_of_measurement=UnitOfEnergy.GIGA_JOULE,
entity_category=EntityCategory.DIAGNOSTIC,
value_fn=lambda res: getattr(res, "heat_previous_year_gj", None),
),
SensorEntityDescription(
HeatMeterSensorEntityDescription(
key="volume_previous_year_m3",
icon="mdi:fire",
name="Volume usage previous year",
device_class=SensorDeviceClass.VOLUME,
native_unit_of_measurement=UnitOfVolume.CUBIC_METERS,
entity_category=EntityCategory.DIAGNOSTIC,
value_fn=lambda res: getattr(res, "volume_previous_year_m3", None),
),
SensorEntityDescription(
HeatMeterSensorEntityDescription(
key="ownership_number",
name="Ownership number",
icon="mdi:identifier",
entity_category=EntityCategory.DIAGNOSTIC,
value_fn=lambda res: getattr(res, "ownership_number", None),
),
SensorEntityDescription(
HeatMeterSensorEntityDescription(
key="error_number",
name="Error number",
icon="mdi:home-alert",
entity_category=EntityCategory.DIAGNOSTIC,
value_fn=lambda res: getattr(res, "error_number", None),
),
SensorEntityDescription(
HeatMeterSensorEntityDescription(
key="device_number",
name="Device number",
icon="mdi:identifier",
entity_category=EntityCategory.DIAGNOSTIC,
value_fn=lambda res: getattr(res, "device_number", None),
),
SensorEntityDescription(
HeatMeterSensorEntityDescription(
key="measurement_period_minutes",
name="Measurement period minutes",
device_class=SensorDeviceClass.DURATION,
native_unit_of_measurement=UnitOfTime.MINUTES,
entity_category=EntityCategory.DIAGNOSTIC,
value_fn=lambda res: getattr(res, "measurement_period_minutes", None),
),
SensorEntityDescription(
HeatMeterSensorEntityDescription(
key="power_max_kw",
name="Power max",
native_unit_of_measurement=UnitOfPower.KILO_WATT,
device_class=SensorDeviceClass.POWER,
entity_category=EntityCategory.DIAGNOSTIC,
value_fn=lambda res: getattr(res, "power_max_kw", None),
),
SensorEntityDescription(
HeatMeterSensorEntityDescription(
key="power_max_previous_year_kw",
name="Power max previous year",
native_unit_of_measurement=UnitOfPower.KILO_WATT,
device_class=SensorDeviceClass.POWER,
entity_category=EntityCategory.DIAGNOSTIC,
value_fn=lambda res: getattr(res, "power_max_previous_year_kw", None),
),
SensorEntityDescription(
HeatMeterSensorEntityDescription(
key="flowrate_max_m3ph",
name="Flowrate max",
native_unit_of_measurement=UnitOfVolumeFlowRate.CUBIC_METERS_PER_HOUR,
icon="mdi:water-outline",
entity_category=EntityCategory.DIAGNOSTIC,
value_fn=lambda res: getattr(res, "flowrate_max_m3ph", None),
),
SensorEntityDescription(
HeatMeterSensorEntityDescription(
key="flowrate_max_previous_year_m3ph",
name="Flowrate max previous year",
native_unit_of_measurement=UnitOfVolumeFlowRate.CUBIC_METERS_PER_HOUR,
icon="mdi:water-outline",
entity_category=EntityCategory.DIAGNOSTIC,
value_fn=lambda res: getattr(res, "flowrate_max_previous_year_m3ph", None),
),
SensorEntityDescription(
HeatMeterSensorEntityDescription(
key="return_temperature_max_c",
name="Return temperature max",
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
device_class=SensorDeviceClass.TEMPERATURE,
entity_category=EntityCategory.DIAGNOSTIC,
value_fn=lambda res: getattr(res, "return_temperature_max_c", None),
),
SensorEntityDescription(
HeatMeterSensorEntityDescription(
key="return_temperature_max_previous_year_c",
name="Return temperature max previous year",
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
device_class=SensorDeviceClass.TEMPERATURE,
entity_category=EntityCategory.DIAGNOSTIC,
value_fn=lambda res: getattr(
res, "return_temperature_max_previous_year_c", None
),
),
SensorEntityDescription(
HeatMeterSensorEntityDescription(
key="flow_temperature_max_c",
name="Flow temperature max",
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
device_class=SensorDeviceClass.TEMPERATURE,
entity_category=EntityCategory.DIAGNOSTIC,
value_fn=lambda res: getattr(res, "flow_temperature_max_c", None),
),
SensorEntityDescription(
HeatMeterSensorEntityDescription(
key="flow_temperature_max_previous_year_c",
name="Flow temperature max previous year",
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
device_class=SensorDeviceClass.TEMPERATURE,
entity_category=EntityCategory.DIAGNOSTIC,
value_fn=lambda res: getattr(res, "flow_temperature_max_previous_year_c", None),
),
SensorEntityDescription(
HeatMeterSensorEntityDescription(
key="operating_hours",
name="Operating hours",
device_class=SensorDeviceClass.DURATION,
native_unit_of_measurement=UnitOfTime.HOURS,
entity_category=EntityCategory.DIAGNOSTIC,
value_fn=lambda res: getattr(res, "operating_hours", None),
),
SensorEntityDescription(
HeatMeterSensorEntityDescription(
key="flow_hours",
name="Flow hours",
device_class=SensorDeviceClass.DURATION,
native_unit_of_measurement=UnitOfTime.HOURS,
entity_category=EntityCategory.DIAGNOSTIC,
value_fn=lambda res: getattr(res, "flow_hours", None),
),
SensorEntityDescription(
HeatMeterSensorEntityDescription(
key="fault_hours",
name="Fault hours",
device_class=SensorDeviceClass.DURATION,
native_unit_of_measurement=UnitOfTime.HOURS,
entity_category=EntityCategory.DIAGNOSTIC,
value_fn=lambda res: getattr(res, "fault_hours", None),
),
SensorEntityDescription(
HeatMeterSensorEntityDescription(
key="fault_hours_previous_year",
name="Fault hours previous year",
device_class=SensorDeviceClass.DURATION,
native_unit_of_measurement=UnitOfTime.HOURS,
entity_category=EntityCategory.DIAGNOSTIC,
value_fn=lambda res: getattr(res, "fault_hours_previous_year", None),
),
SensorEntityDescription(
HeatMeterSensorEntityDescription(
key="yearly_set_day",
name="Yearly set day",
icon="mdi:clock-outline",
entity_category=EntityCategory.DIAGNOSTIC,
value_fn=lambda res: getattr(res, "yearly_set_day", None),
),
SensorEntityDescription(
HeatMeterSensorEntityDescription(
key="monthly_set_day",
name="Monthly set day",
icon="mdi:clock-outline",
entity_category=EntityCategory.DIAGNOSTIC,
value_fn=lambda res: getattr(res, "monthly_set_day", None),
),
SensorEntityDescription(
HeatMeterSensorEntityDescription(
key="meter_date_time",
name="Meter date time",
icon="mdi:clock-outline",
device_class=SensorDeviceClass.TIMESTAMP,
entity_category=EntityCategory.DIAGNOSTIC,
value_fn=lambda res: dt_util.as_utc(res.meter_date_time)
if res.meter_date_time
else None,
),
SensorEntityDescription(
HeatMeterSensorEntityDescription(
key="measuring_range_m3ph",
name="Measuring range",
native_unit_of_measurement=UnitOfVolumeFlowRate.CUBIC_METERS_PER_HOUR,
icon="mdi:water-outline",
entity_category=EntityCategory.DIAGNOSTIC,
value_fn=lambda res: getattr(res, "measuring_range_m3ph", None),
),
SensorEntityDescription(
HeatMeterSensorEntityDescription(
key="settings_and_firmware",
name="Settings and firmware",
entity_category=EntityCategory.DIAGNOSTIC,
value_fn=lambda res: getattr(res, "settings_and_firmware", None),
),
)
@ -238,14 +284,17 @@ async def async_setup_entry(
class HeatMeterSensor(
CoordinatorEntity[DataUpdateCoordinator[HeatMeterResponse]], RestoreSensor
CoordinatorEntity[DataUpdateCoordinator[HeatMeterResponse]],
SensorEntity,
):
"""Representation of a Sensor."""
entity_description: HeatMeterSensorEntityDescription
def __init__(
self,
coordinator: DataUpdateCoordinator[HeatMeterResponse],
description: SensorEntityDescription,
description: HeatMeterSensorEntityDescription,
device: DeviceInfo,
) -> None:
"""Set up the sensor with the initial values."""
@ -254,25 +303,9 @@ class HeatMeterSensor(
self._attr_unique_id = f"{coordinator.config_entry.data['device_number']}_{description.key}" # type: ignore[union-attr]
self._attr_name = f"Heat Meter {description.name}"
self.entity_description = description
self._attr_device_info = device
self._attr_should_poll = bool(self.key in ("heat_usage", "heat_previous_year"))
async def async_added_to_hass(self) -> None:
"""Call when entity about to be added to hass."""
await super().async_added_to_hass()
state = await self.async_get_last_sensor_data()
if state:
self._attr_native_value = state.native_value
def _handle_coordinator_update(self) -> None:
"""Handle updated data from the coordinator."""
if self.key in asdict(self.coordinator.data):
if self.device_class == SensorDeviceClass.TIMESTAMP:
self._attr_native_value = dt_util.as_utc(
asdict(self.coordinator.data)[self.key]
)
else:
self._attr_native_value = asdict(self.coordinator.data)[self.key]
self.async_write_ha_state()
@property
def native_value(self) -> StateType | datetime:
"""Return the state of the sensor."""
return self.entity_description.value_fn(self.coordinator.data)

View File

@ -5,20 +5,15 @@ from unittest.mock import patch
import serial
from homeassistant.components.homeassistant import (
DOMAIN as HA_DOMAIN,
SERVICE_UPDATE_ENTITY,
)
from homeassistant.components.homeassistant import DOMAIN as HA_DOMAIN
from homeassistant.components.landisgyr_heat_meter.const import DOMAIN, POLLING_INTERVAL
from homeassistant.components.sensor import (
ATTR_LAST_RESET,
ATTR_STATE_CLASS,
SensorDeviceClass,
SensorStateClass,
)
from homeassistant.const import (
ATTR_DEVICE_CLASS,
ATTR_ENTITY_ID,
ATTR_ICON,
ATTR_UNIT_OF_MEASUREMENT,
STATE_UNAVAILABLE,
@ -26,16 +21,12 @@ from homeassistant.const import (
UnitOfEnergy,
UnitOfVolume,
)
from homeassistant.core import CoreState, HomeAssistant, State
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from homeassistant.setup import async_setup_component
from homeassistant.util import dt as dt_util
from tests.common import (
MockConfigEntry,
async_fire_time_changed,
mock_restore_cache_with_extra_data,
)
from tests.common import MockConfigEntry, async_fire_time_changed
API_HEAT_METER_SERVICE = (
"homeassistant.components.landisgyr_heat_meter.ultraheat_api.HeatMeterService"
@ -80,13 +71,6 @@ async def test_create_sensors(
await hass.config_entries.async_setup(mock_entry.entry_id)
await async_setup_component(hass, HA_DOMAIN, {})
await hass.async_block_till_done()
await hass.services.async_call(
HA_DOMAIN,
SERVICE_UPDATE_ENTITY,
{ATTR_ENTITY_ID: "sensor.heat_meter_heat_usage_gj"},
blocking=True,
)
await hass.async_block_till_done()
# check if 26 attributes have been created
assert len(hass.states.async_all()) == 25
@ -121,97 +105,6 @@ async def test_create_sensors(
assert entity_registry_entry.entity_category == EntityCategory.DIAGNOSTIC
@patch(API_HEAT_METER_SERVICE)
async def test_restore_state(mock_heat_meter, hass: HomeAssistant) -> None:
"""Test sensor restore state."""
# Home assistant is not running yet
hass.state = CoreState.not_running
last_reset = "2022-07-01T00:00:00.000000+00:00"
mock_restore_cache_with_extra_data(
hass,
[
(
State(
"sensor.heat_meter_heat_usage_gj",
"34167",
attributes={
ATTR_LAST_RESET: last_reset,
ATTR_UNIT_OF_MEASUREMENT: UnitOfEnergy.GIGA_JOULE,
ATTR_STATE_CLASS: SensorStateClass.TOTAL,
},
),
{
"native_value": 34167,
"native_unit_of_measurement": UnitOfEnergy.GIGA_JOULE,
"icon": "mdi:fire",
"last_reset": last_reset,
},
),
(
State(
"sensor.heat_meter_volume_usage",
"456",
attributes={
ATTR_LAST_RESET: last_reset,
ATTR_UNIT_OF_MEASUREMENT: UnitOfVolume.CUBIC_METERS,
ATTR_STATE_CLASS: SensorStateClass.TOTAL,
},
),
{
"native_value": 456,
"native_unit_of_measurement": UnitOfVolume.CUBIC_METERS,
"icon": "mdi:fire",
"last_reset": last_reset,
},
),
(
State(
"sensor.heat_meter_device_number",
"devicenr_789",
attributes={
ATTR_LAST_RESET: last_reset,
},
),
{
"native_value": "devicenr_789",
"native_unit_of_measurement": None,
"last_reset": last_reset,
},
),
],
)
entry_data = {
"device": "/dev/USB0",
"model": "LUGCUH50",
"device_number": "123456789",
}
# create and add entry
mock_entry = MockConfigEntry(domain=DOMAIN, unique_id=DOMAIN, data=entry_data)
mock_entry.add_to_hass(hass)
await hass.config_entries.async_setup(mock_entry.entry_id)
await hass.async_block_till_done()
# restore from cache
state = hass.states.get("sensor.heat_meter_heat_usage_gj")
assert state
assert state.state == "34167"
assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == UnitOfEnergy.GIGA_JOULE
assert state.attributes.get(ATTR_STATE_CLASS) == SensorStateClass.TOTAL
state = hass.states.get("sensor.heat_meter_volume_usage")
assert state
assert state.state == "456"
assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == UnitOfVolume.CUBIC_METERS
assert state.attributes.get(ATTR_STATE_CLASS) == SensorStateClass.TOTAL
state = hass.states.get("sensor.heat_meter_device_number")
assert state
assert state.state == "devicenr_789"
assert state.attributes.get(ATTR_STATE_CLASS) is None
@patch(API_HEAT_METER_SERVICE)
async def test_exception_on_polling(mock_heat_meter, hass: HomeAssistant) -> None:
"""Test sensor."""
@ -237,13 +130,6 @@ async def test_exception_on_polling(mock_heat_meter, hass: HomeAssistant) -> Non
await hass.config_entries.async_setup(mock_entry.entry_id)
await async_setup_component(hass, HA_DOMAIN, {})
await hass.async_block_till_done()
await hass.services.async_call(
HA_DOMAIN,
SERVICE_UPDATE_ENTITY,
{ATTR_ENTITY_ID: "sensor.heat_meter_heat_usage_gj"},
blocking=True,
)
await hass.async_block_till_done()
# check if initial setup succeeded
state = hass.states.get("sensor.heat_meter_heat_usage_gj")