mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 21:27:38 +00:00
Add iotawatt high-accuracy energy readout sensors (#55512)
This commit is contained in:
parent
011817b122
commit
556dcf6abb
@ -248,7 +248,7 @@ homeassistant/components/integration/* @dgomes
|
||||
homeassistant/components/intent/* @home-assistant/core
|
||||
homeassistant/components/intesishome/* @jnimmo
|
||||
homeassistant/components/ios/* @robbiet480
|
||||
homeassistant/components/iotawatt/* @gtdiehl
|
||||
homeassistant/components/iotawatt/* @gtdiehl @jyavenard
|
||||
homeassistant/components/iperf3/* @rohankapoorcom
|
||||
homeassistant/components/ipma/* @dgomes @abmantis
|
||||
homeassistant/components/ipp/* @ctalkington
|
||||
|
@ -9,4 +9,6 @@ DOMAIN = "iotawatt"
|
||||
VOLT_AMPERE_REACTIVE = "VAR"
|
||||
VOLT_AMPERE_REACTIVE_HOURS = "VARh"
|
||||
|
||||
ATTR_LAST_UPDATE = "last_update"
|
||||
|
||||
CONNECTION_ERRORS = (KeyError, json.JSONDecodeError, httpx.HTTPError)
|
||||
|
@ -1,7 +1,7 @@
|
||||
"""IoTaWatt DataUpdateCoordinator."""
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import timedelta
|
||||
from datetime import datetime, timedelta
|
||||
import logging
|
||||
|
||||
from iotawattpy.iotawatt import Iotawatt
|
||||
@ -32,6 +32,16 @@ class IotawattUpdater(DataUpdateCoordinator):
|
||||
update_interval=timedelta(seconds=30),
|
||||
)
|
||||
|
||||
self._last_run: datetime | None = None
|
||||
|
||||
def update_last_run(self, last_run: datetime) -> None:
|
||||
"""Notify coordinator of a sensor last update time."""
|
||||
# We want to fetch the data from the iotawatt since HA was last shutdown.
|
||||
# We retrieve from the sensor last updated.
|
||||
# This method is called from each sensor upon their state being restored.
|
||||
if self._last_run is None or last_run > self._last_run:
|
||||
self._last_run = last_run
|
||||
|
||||
async def _async_update_data(self):
|
||||
"""Fetch sensors from IoTaWatt device."""
|
||||
if self.api is None:
|
||||
@ -52,5 +62,6 @@ class IotawattUpdater(DataUpdateCoordinator):
|
||||
|
||||
self.api = api
|
||||
|
||||
await self.api.update()
|
||||
await self.api.update(lastUpdate=self._last_run)
|
||||
self._last_run = None
|
||||
return self.api.getSensors()
|
||||
|
@ -4,10 +4,11 @@
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/iotawatt",
|
||||
"requirements": [
|
||||
"iotawattpy==0.0.8"
|
||||
"iotawattpy==0.1.0"
|
||||
],
|
||||
"codeowners": [
|
||||
"@gtdiehl"
|
||||
"@gtdiehl",
|
||||
"@jyavenard"
|
||||
],
|
||||
"iot_class": "local_polling"
|
||||
}
|
@ -2,12 +2,14 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
import logging
|
||||
from typing import Callable
|
||||
|
||||
from iotawattpy.sensor import Sensor
|
||||
|
||||
from homeassistant.components.sensor import (
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
STATE_CLASS_TOTAL_INCREASING,
|
||||
SensorEntity,
|
||||
SensorEntityDescription,
|
||||
)
|
||||
@ -28,10 +30,19 @@ from homeassistant.const import (
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers import entity, entity_registry, update_coordinator
|
||||
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
|
||||
from homeassistant.helpers.restore_state import RestoreEntity
|
||||
from homeassistant.util import dt
|
||||
|
||||
from .const import DOMAIN, VOLT_AMPERE_REACTIVE, VOLT_AMPERE_REACTIVE_HOURS
|
||||
from .const import (
|
||||
ATTR_LAST_UPDATE,
|
||||
DOMAIN,
|
||||
VOLT_AMPERE_REACTIVE,
|
||||
VOLT_AMPERE_REACTIVE_HOURS,
|
||||
)
|
||||
from .coordinator import IotawattUpdater
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclass
|
||||
class IotaWattSensorEntityDescription(SensorEntityDescription):
|
||||
@ -114,15 +125,19 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
def _create_entity(key: str) -> IotaWattSensor:
|
||||
"""Create a sensor entity."""
|
||||
created.add(key)
|
||||
data = coordinator.data["sensors"][key]
|
||||
description = ENTITY_DESCRIPTION_KEY_MAP.get(
|
||||
data.getUnit(), IotaWattSensorEntityDescription("base_sensor")
|
||||
)
|
||||
if data.getUnit() == "WattHours" and not data.getFromStart():
|
||||
return IotaWattAccumulatingSensor(
|
||||
coordinator=coordinator, key=key, entity_description=description
|
||||
)
|
||||
|
||||
return IotaWattSensor(
|
||||
coordinator=coordinator,
|
||||
key=key,
|
||||
mac_address=coordinator.data["sensors"][key].hub_mac_address,
|
||||
name=coordinator.data["sensors"][key].getName(),
|
||||
entity_description=ENTITY_DESCRIPTION_KEY_MAP.get(
|
||||
coordinator.data["sensors"][key].getUnit(),
|
||||
IotaWattSensorEntityDescription("base_sensor"),
|
||||
),
|
||||
entity_description=description,
|
||||
)
|
||||
|
||||
async_add_entities(_create_entity(key) for key in coordinator.data["sensors"])
|
||||
@ -145,16 +160,14 @@ class IotaWattSensor(update_coordinator.CoordinatorEntity, SensorEntity):
|
||||
"""Defines a IoTaWatt Energy Sensor."""
|
||||
|
||||
entity_description: IotaWattSensorEntityDescription
|
||||
_attr_force_update = True
|
||||
coordinator: IotawattUpdater
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator,
|
||||
key,
|
||||
mac_address,
|
||||
name,
|
||||
coordinator: IotawattUpdater,
|
||||
key: str,
|
||||
entity_description: IotaWattSensorEntityDescription,
|
||||
):
|
||||
) -> None:
|
||||
"""Initialize the sensor."""
|
||||
super().__init__(coordinator=coordinator)
|
||||
|
||||
@ -196,17 +209,15 @@ class IotaWattSensor(update_coordinator.CoordinatorEntity, SensorEntity):
|
||||
else:
|
||||
self.hass.async_create_task(self.async_remove())
|
||||
return
|
||||
|
||||
super()._handle_coordinator_update()
|
||||
|
||||
@property
|
||||
def extra_state_attributes(self):
|
||||
def extra_state_attributes(self) -> dict[str, str]:
|
||||
"""Return the extra state attributes of the entity."""
|
||||
data = self._sensor_data
|
||||
attrs = {"type": data.getType()}
|
||||
if attrs["type"] == "Input":
|
||||
attrs["channel"] = data.getChannel()
|
||||
|
||||
return attrs
|
||||
|
||||
@property
|
||||
@ -216,3 +227,78 @@ class IotaWattSensor(update_coordinator.CoordinatorEntity, SensorEntity):
|
||||
return func(self._sensor_data.getValue())
|
||||
|
||||
return self._sensor_data.getValue()
|
||||
|
||||
|
||||
class IotaWattAccumulatingSensor(IotaWattSensor, RestoreEntity):
|
||||
"""Defines a IoTaWatt Accumulative Energy (High Accuracy) Sensor."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: IotawattUpdater,
|
||||
key: str,
|
||||
entity_description: IotaWattSensorEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize the sensor."""
|
||||
|
||||
super().__init__(coordinator, key, entity_description)
|
||||
|
||||
self._attr_state_class = STATE_CLASS_TOTAL_INCREASING
|
||||
if self._attr_unique_id is not None:
|
||||
self._attr_unique_id += ".accumulated"
|
||||
|
||||
self._accumulated_value: float | None = None
|
||||
|
||||
@callback
|
||||
def _handle_coordinator_update(self) -> None:
|
||||
"""Handle updated data from the coordinator."""
|
||||
assert (
|
||||
self._accumulated_value is not None
|
||||
), "async_added_to_hass must have been called first"
|
||||
self._accumulated_value += float(self._sensor_data.getValue())
|
||||
|
||||
super()._handle_coordinator_update()
|
||||
|
||||
@property
|
||||
def native_value(self) -> entity.StateType:
|
||||
"""Return the state of the sensor."""
|
||||
if self._accumulated_value is None:
|
||||
return None
|
||||
return round(self._accumulated_value, 1)
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Load the last known state value of the entity if the accumulated type."""
|
||||
await super().async_added_to_hass()
|
||||
state = await self.async_get_last_state()
|
||||
self._accumulated_value = 0.0
|
||||
if state:
|
||||
try:
|
||||
# Previous value could be `unknown` if the connection didn't originally
|
||||
# complete.
|
||||
self._accumulated_value = float(state.state)
|
||||
except (ValueError) as err:
|
||||
_LOGGER.warning("Could not restore last state: %s", err)
|
||||
else:
|
||||
if ATTR_LAST_UPDATE in state.attributes:
|
||||
last_run = dt.parse_datetime(state.attributes[ATTR_LAST_UPDATE])
|
||||
if last_run is not None:
|
||||
self.coordinator.update_last_run(last_run)
|
||||
# Force a second update from the iotawatt to ensure that sensors are up to date.
|
||||
await self.coordinator.async_request_refresh()
|
||||
|
||||
@property
|
||||
def name(self) -> str | None:
|
||||
"""Return name of the entity."""
|
||||
return f"{self._sensor_data.getSourceName()} Accumulated"
|
||||
|
||||
@property
|
||||
def extra_state_attributes(self) -> dict[str, str]:
|
||||
"""Return the extra state attributes of the entity."""
|
||||
attrs = super().extra_state_attributes
|
||||
|
||||
assert (
|
||||
self.coordinator.api is not None
|
||||
and self.coordinator.api.getLastUpdateTime() is not None
|
||||
)
|
||||
attrs[ATTR_LAST_UPDATE] = self.coordinator.api.getLastUpdateTime().isoformat()
|
||||
|
||||
return attrs
|
||||
|
@ -865,7 +865,7 @@ influxdb-client==1.14.0
|
||||
influxdb==5.2.3
|
||||
|
||||
# homeassistant.components.iotawatt
|
||||
iotawattpy==0.0.8
|
||||
iotawattpy==0.1.0
|
||||
|
||||
# homeassistant.components.iperf3
|
||||
iperf3==0.1.11
|
||||
|
@ -505,7 +505,7 @@ influxdb-client==1.14.0
|
||||
influxdb==5.2.3
|
||||
|
||||
# homeassistant.components.iotawatt
|
||||
iotawattpy==0.0.8
|
||||
iotawattpy==0.1.0
|
||||
|
||||
# homeassistant.components.gogogate2
|
||||
ismartgate==4.0.0
|
||||
|
@ -3,19 +3,46 @@ from iotawattpy.sensor import Sensor
|
||||
|
||||
INPUT_SENSOR = Sensor(
|
||||
channel="1",
|
||||
name="My Sensor",
|
||||
base_name="My Sensor",
|
||||
suffix=None,
|
||||
io_type="Input",
|
||||
unit="WattHours",
|
||||
value="23",
|
||||
unit="Watts",
|
||||
value=23,
|
||||
begin="",
|
||||
mac_addr="mock-mac",
|
||||
)
|
||||
OUTPUT_SENSOR = Sensor(
|
||||
channel="N/A",
|
||||
name="My WattHour Sensor",
|
||||
base_name="My WattHour Sensor",
|
||||
suffix=None,
|
||||
io_type="Output",
|
||||
unit="WattHours",
|
||||
value="243",
|
||||
value=243,
|
||||
begin="",
|
||||
mac_addr="mock-mac",
|
||||
fromStart=True,
|
||||
)
|
||||
|
||||
INPUT_ACCUMULATED_SENSOR = Sensor(
|
||||
channel="N/A",
|
||||
base_name="My WattHour Accumulated Input Sensor",
|
||||
suffix=".wh",
|
||||
io_type="Input",
|
||||
unit="WattHours",
|
||||
value=500,
|
||||
begin="",
|
||||
mac_addr="mock-mac",
|
||||
fromStart=False,
|
||||
)
|
||||
|
||||
OUTPUT_ACCUMULATED_SENSOR = Sensor(
|
||||
channel="N/A",
|
||||
base_name="My WattHour Accumulated Output Sensor",
|
||||
suffix=".wh",
|
||||
io_type="Output",
|
||||
unit="WattHours",
|
||||
value=200,
|
||||
begin="",
|
||||
mac_addr="mock-mac",
|
||||
fromStart=False,
|
||||
)
|
||||
|
@ -1,19 +1,33 @@
|
||||
"""Test setting up sensors."""
|
||||
from datetime import timedelta
|
||||
|
||||
from homeassistant.components.sensor import ATTR_STATE_CLASS, DEVICE_CLASS_ENERGY
|
||||
from homeassistant.components.iotawatt.const import ATTR_LAST_UPDATE
|
||||
from homeassistant.components.sensor import (
|
||||
ATTR_STATE_CLASS,
|
||||
DEVICE_CLASS_ENERGY,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
STATE_CLASS_TOTAL_INCREASING,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
ATTR_DEVICE_CLASS,
|
||||
ATTR_FRIENDLY_NAME,
|
||||
ATTR_UNIT_OF_MEASUREMENT,
|
||||
DEVICE_CLASS_POWER,
|
||||
ENERGY_WATT_HOUR,
|
||||
POWER_WATT,
|
||||
)
|
||||
from homeassistant.core import State
|
||||
from homeassistant.setup import async_setup_component
|
||||
from homeassistant.util.dt import utcnow
|
||||
|
||||
from . import INPUT_SENSOR, OUTPUT_SENSOR
|
||||
from . import (
|
||||
INPUT_ACCUMULATED_SENSOR,
|
||||
INPUT_SENSOR,
|
||||
OUTPUT_ACCUMULATED_SENSOR,
|
||||
OUTPUT_SENSOR,
|
||||
)
|
||||
|
||||
from tests.common import async_fire_time_changed
|
||||
from tests.common import async_fire_time_changed, mock_restore_cache
|
||||
|
||||
|
||||
async def test_sensor_type_input(hass, mock_iotawatt):
|
||||
@ -33,10 +47,10 @@ async def test_sensor_type_input(hass, mock_iotawatt):
|
||||
state = hass.states.get("sensor.my_sensor")
|
||||
assert state is not None
|
||||
assert state.state == "23"
|
||||
assert ATTR_STATE_CLASS not in state.attributes
|
||||
assert state.attributes[ATTR_STATE_CLASS] == STATE_CLASS_MEASUREMENT
|
||||
assert state.attributes[ATTR_FRIENDLY_NAME] == "My Sensor"
|
||||
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == ENERGY_WATT_HOUR
|
||||
assert state.attributes[ATTR_DEVICE_CLASS] == DEVICE_CLASS_ENERGY
|
||||
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == POWER_WATT
|
||||
assert state.attributes[ATTR_DEVICE_CLASS] == DEVICE_CLASS_POWER
|
||||
assert state.attributes["channel"] == "1"
|
||||
assert state.attributes["type"] == "Input"
|
||||
|
||||
@ -60,6 +74,7 @@ async def test_sensor_type_output(hass, mock_iotawatt):
|
||||
state = hass.states.get("sensor.my_watthour_sensor")
|
||||
assert state is not None
|
||||
assert state.state == "243"
|
||||
assert ATTR_STATE_CLASS not in state.attributes
|
||||
assert state.attributes[ATTR_FRIENDLY_NAME] == "My WattHour Sensor"
|
||||
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == ENERGY_WATT_HOUR
|
||||
assert state.attributes[ATTR_DEVICE_CLASS] == DEVICE_CLASS_ENERGY
|
||||
@ -70,3 +85,161 @@ async def test_sensor_type_output(hass, mock_iotawatt):
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert hass.states.get("sensor.my_watthour_sensor") is None
|
||||
|
||||
|
||||
async def test_sensor_type_accumulated_output(hass, mock_iotawatt):
|
||||
"""Tests the sensor type of Accumulated Output and that it's properly restored from saved state."""
|
||||
mock_iotawatt.getSensors.return_value["sensors"][
|
||||
"my_watthour_accumulated_output_sensor_key"
|
||||
] = OUTPUT_ACCUMULATED_SENSOR
|
||||
|
||||
DUMMY_DATE = "2021-09-01T14:00:00+10:00"
|
||||
|
||||
mock_restore_cache(
|
||||
hass,
|
||||
(
|
||||
State(
|
||||
"sensor.my_watthour_accumulated_output_sensor_wh_accumulated",
|
||||
"100.0",
|
||||
{
|
||||
"device_class": DEVICE_CLASS_ENERGY,
|
||||
"unit_of_measurement": ENERGY_WATT_HOUR,
|
||||
"last_update": DUMMY_DATE,
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
assert await async_setup_component(hass, "iotawatt", {})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(hass.states.async_entity_ids()) == 1
|
||||
|
||||
state = hass.states.get(
|
||||
"sensor.my_watthour_accumulated_output_sensor_wh_accumulated"
|
||||
)
|
||||
assert state is not None
|
||||
|
||||
assert state.state == "300.0" # 100 + 200
|
||||
assert (
|
||||
state.attributes[ATTR_FRIENDLY_NAME]
|
||||
== "My WattHour Accumulated Output Sensor.wh Accumulated"
|
||||
)
|
||||
assert state.attributes[ATTR_STATE_CLASS] == STATE_CLASS_TOTAL_INCREASING
|
||||
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == ENERGY_WATT_HOUR
|
||||
assert state.attributes[ATTR_DEVICE_CLASS] == DEVICE_CLASS_ENERGY
|
||||
assert state.attributes["type"] == "Output"
|
||||
assert state.attributes[ATTR_LAST_UPDATE] is not None
|
||||
assert state.attributes[ATTR_LAST_UPDATE] != DUMMY_DATE
|
||||
|
||||
|
||||
async def test_sensor_type_accumulated_output_error_restore(hass, mock_iotawatt):
|
||||
"""Tests the sensor type of Accumulated Output and that it's properly restored from saved state."""
|
||||
mock_iotawatt.getSensors.return_value["sensors"][
|
||||
"my_watthour_accumulated_output_sensor_key"
|
||||
] = OUTPUT_ACCUMULATED_SENSOR
|
||||
|
||||
DUMMY_DATE = "2021-09-01T14:00:00+10:00"
|
||||
|
||||
mock_restore_cache(
|
||||
hass,
|
||||
(
|
||||
State(
|
||||
"sensor.my_watthour_accumulated_output_sensor_wh_accumulated",
|
||||
"unknown",
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
assert await async_setup_component(hass, "iotawatt", {})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(hass.states.async_entity_ids()) == 1
|
||||
|
||||
state = hass.states.get(
|
||||
"sensor.my_watthour_accumulated_output_sensor_wh_accumulated"
|
||||
)
|
||||
assert state is not None
|
||||
|
||||
assert state.state == "200.0" # Returns the new read as restore failed.
|
||||
assert (
|
||||
state.attributes[ATTR_FRIENDLY_NAME]
|
||||
== "My WattHour Accumulated Output Sensor.wh Accumulated"
|
||||
)
|
||||
assert state.attributes[ATTR_STATE_CLASS] == STATE_CLASS_TOTAL_INCREASING
|
||||
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == ENERGY_WATT_HOUR
|
||||
assert state.attributes[ATTR_DEVICE_CLASS] == DEVICE_CLASS_ENERGY
|
||||
assert state.attributes["type"] == "Output"
|
||||
assert state.attributes[ATTR_LAST_UPDATE] is not None
|
||||
assert state.attributes[ATTR_LAST_UPDATE] != DUMMY_DATE
|
||||
|
||||
|
||||
async def test_sensor_type_multiple_accumulated_output(hass, mock_iotawatt):
|
||||
"""Tests the sensor type of Accumulated Output and that it's properly restored from saved state."""
|
||||
mock_iotawatt.getSensors.return_value["sensors"][
|
||||
"my_watthour_accumulated_output_sensor_key"
|
||||
] = OUTPUT_ACCUMULATED_SENSOR
|
||||
mock_iotawatt.getSensors.return_value["sensors"][
|
||||
"my_watthour_accumulated_input_sensor_key"
|
||||
] = INPUT_ACCUMULATED_SENSOR
|
||||
|
||||
DUMMY_DATE = "2021-09-01T14:00:00+10:00"
|
||||
|
||||
mock_restore_cache(
|
||||
hass,
|
||||
(
|
||||
State(
|
||||
"sensor.my_watthour_accumulated_output_sensor_wh_accumulated",
|
||||
"100.0",
|
||||
{
|
||||
"device_class": DEVICE_CLASS_ENERGY,
|
||||
"unit_of_measurement": ENERGY_WATT_HOUR,
|
||||
"last_update": DUMMY_DATE,
|
||||
},
|
||||
),
|
||||
State(
|
||||
"sensor.my_watthour_accumulated_input_sensor_wh_accumulated",
|
||||
"50.0",
|
||||
{
|
||||
"device_class": DEVICE_CLASS_ENERGY,
|
||||
"unit_of_measurement": ENERGY_WATT_HOUR,
|
||||
"last_update": DUMMY_DATE,
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
assert await async_setup_component(hass, "iotawatt", {})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(hass.states.async_entity_ids()) == 2
|
||||
|
||||
state = hass.states.get(
|
||||
"sensor.my_watthour_accumulated_output_sensor_wh_accumulated"
|
||||
)
|
||||
assert state is not None
|
||||
|
||||
assert state.state == "300.0" # 100 + 200
|
||||
assert (
|
||||
state.attributes[ATTR_FRIENDLY_NAME]
|
||||
== "My WattHour Accumulated Output Sensor.wh Accumulated"
|
||||
)
|
||||
assert state.attributes[ATTR_STATE_CLASS] == STATE_CLASS_TOTAL_INCREASING
|
||||
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == ENERGY_WATT_HOUR
|
||||
assert state.attributes[ATTR_DEVICE_CLASS] == DEVICE_CLASS_ENERGY
|
||||
assert state.attributes["type"] == "Output"
|
||||
assert state.attributes[ATTR_LAST_UPDATE] is not None
|
||||
assert state.attributes[ATTR_LAST_UPDATE] != DUMMY_DATE
|
||||
|
||||
state = hass.states.get(
|
||||
"sensor.my_watthour_accumulated_input_sensor_wh_accumulated"
|
||||
)
|
||||
assert state is not None
|
||||
|
||||
assert state.state == "550.0" # 50 + 500
|
||||
assert (
|
||||
state.attributes[ATTR_FRIENDLY_NAME]
|
||||
== "My WattHour Accumulated Input Sensor.wh Accumulated"
|
||||
)
|
||||
assert state.attributes[ATTR_LAST_UPDATE] is not None
|
||||
assert state.attributes[ATTR_LAST_UPDATE] != DUMMY_DATE
|
||||
|
Loading…
x
Reference in New Issue
Block a user