mirror of https://github.com/home-assistant/core
Add Landis+Gyr poll on restart (#89644)
This commit is contained in:
parent
d106cb48d2
commit
6ad9f420ab
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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")
|
||||
|
|
Loading…
Reference in New Issue