mirror of
https://github.com/home-assistant/core.git
synced 2025-07-22 20:57:21 +00:00
Add check for valid initial_suggested_unit (#108902)
Co-authored-by: Erik Montnemery <erik@montnemery.com>
This commit is contained in:
parent
c363edad4a
commit
7d2c6a1bb6
@ -219,6 +219,7 @@ class SensorEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
|||||||
_last_reset_reported = False
|
_last_reset_reported = False
|
||||||
_sensor_option_display_precision: int | None = None
|
_sensor_option_display_precision: int | None = None
|
||||||
_sensor_option_unit_of_measurement: str | None | UndefinedType = UNDEFINED
|
_sensor_option_unit_of_measurement: str | None | UndefinedType = UNDEFINED
|
||||||
|
_invalid_suggested_unit_of_measurement_reported = False
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def add_to_platform_start(
|
def add_to_platform_start(
|
||||||
@ -376,6 +377,34 @@ class SensorEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
|||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def _is_valid_suggested_unit(self, suggested_unit_of_measurement: str) -> bool:
|
||||||
|
"""Validate the suggested unit.
|
||||||
|
|
||||||
|
Validate that a unit converter exists for the sensor's device class and that the
|
||||||
|
unit converter supports both the native and the suggested units of measurement.
|
||||||
|
"""
|
||||||
|
# Make sure we can convert the units
|
||||||
|
if (
|
||||||
|
(unit_converter := UNIT_CONVERTERS.get(self.device_class)) is None
|
||||||
|
or self.native_unit_of_measurement not in unit_converter.VALID_UNITS
|
||||||
|
or suggested_unit_of_measurement not in unit_converter.VALID_UNITS
|
||||||
|
):
|
||||||
|
if not self._invalid_suggested_unit_of_measurement_reported:
|
||||||
|
self._invalid_suggested_unit_of_measurement_reported = True
|
||||||
|
report_issue = self._suggest_report_issue()
|
||||||
|
# This should raise in Home Assistant Core 2024.5
|
||||||
|
_LOGGER.warning(
|
||||||
|
(
|
||||||
|
"%s sets an invalid suggested_unit_of_measurement. Please %s. "
|
||||||
|
"This warning will become an error in Home Assistant Core 2024.5"
|
||||||
|
),
|
||||||
|
type(self),
|
||||||
|
report_issue,
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
def _get_initial_suggested_unit(self) -> str | UndefinedType:
|
def _get_initial_suggested_unit(self) -> str | UndefinedType:
|
||||||
"""Return the initial unit."""
|
"""Return the initial unit."""
|
||||||
# Unit suggested by the integration
|
# Unit suggested by the integration
|
||||||
@ -390,6 +419,10 @@ class SensorEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
|||||||
if suggested_unit_of_measurement is None:
|
if suggested_unit_of_measurement is None:
|
||||||
return UNDEFINED
|
return UNDEFINED
|
||||||
|
|
||||||
|
# Make sure we can convert the units
|
||||||
|
if not self._is_valid_suggested_unit(suggested_unit_of_measurement):
|
||||||
|
return UNDEFINED
|
||||||
|
|
||||||
return suggested_unit_of_measurement
|
return suggested_unit_of_measurement
|
||||||
|
|
||||||
def get_initial_entity_options(self) -> er.EntityOptionsType | None:
|
def get_initial_entity_options(self) -> er.EntityOptionsType | None:
|
||||||
@ -486,16 +519,17 @@ class SensorEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
|||||||
if self._sensor_option_unit_of_measurement is not UNDEFINED:
|
if self._sensor_option_unit_of_measurement is not UNDEFINED:
|
||||||
return self._sensor_option_unit_of_measurement
|
return self._sensor_option_unit_of_measurement
|
||||||
|
|
||||||
|
native_unit_of_measurement = self.native_unit_of_measurement
|
||||||
|
|
||||||
# Second priority, for non registered entities: unit suggested by integration
|
# Second priority, for non registered entities: unit suggested by integration
|
||||||
if not self.registry_entry and (
|
if not self.registry_entry and (
|
||||||
suggested_unit_of_measurement := self.suggested_unit_of_measurement
|
suggested_unit_of_measurement := self.suggested_unit_of_measurement
|
||||||
):
|
):
|
||||||
return suggested_unit_of_measurement
|
if self._is_valid_suggested_unit(suggested_unit_of_measurement):
|
||||||
|
return suggested_unit_of_measurement
|
||||||
|
|
||||||
# Third priority: Legacy temperature conversion, which applies
|
# Third priority: Legacy temperature conversion, which applies
|
||||||
# to both registered and non registered entities
|
# to both registered and non registered entities
|
||||||
native_unit_of_measurement = self.native_unit_of_measurement
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
native_unit_of_measurement in TEMPERATURE_UNITS
|
native_unit_of_measurement in TEMPERATURE_UNITS
|
||||||
and self.device_class is SensorDeviceClass.TEMPERATURE
|
and self.device_class is SensorDeviceClass.TEMPERATURE
|
||||||
|
@ -326,9 +326,6 @@
|
|||||||
'id': <ANY>,
|
'id': <ANY>,
|
||||||
'name': None,
|
'name': None,
|
||||||
'options': dict({
|
'options': dict({
|
||||||
'sensor.private': dict({
|
|
||||||
'suggested_unit_of_measurement': <UnitOfTime.MINUTES: 'min'>,
|
|
||||||
}),
|
|
||||||
}),
|
}),
|
||||||
'original_device_class': None,
|
'original_device_class': None,
|
||||||
'original_icon': None,
|
'original_icon': None,
|
||||||
@ -338,7 +335,7 @@
|
|||||||
'supported_features': 0,
|
'supported_features': 0,
|
||||||
'translation_key': 'stats_time',
|
'translation_key': 'stats_time',
|
||||||
'unique_id': 'E1234567890000000001_stats_time',
|
'unique_id': 'E1234567890000000001_stats_time',
|
||||||
'unit_of_measurement': <UnitOfTime.MINUTES: 'min'>,
|
'unit_of_measurement': <UnitOfTime.SECONDS: 's'>,
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_sensors[yna5x1-entity_ids0][sensor.ozmo_950_time_cleaned:state]
|
# name: test_sensors[yna5x1-entity_ids0][sensor.ozmo_950_time_cleaned:state]
|
||||||
@ -468,9 +465,6 @@
|
|||||||
'id': <ANY>,
|
'id': <ANY>,
|
||||||
'name': None,
|
'name': None,
|
||||||
'options': dict({
|
'options': dict({
|
||||||
'sensor.private': dict({
|
|
||||||
'suggested_unit_of_measurement': <UnitOfTime.HOURS: 'h'>,
|
|
||||||
}),
|
|
||||||
}),
|
}),
|
||||||
'original_device_class': None,
|
'original_device_class': None,
|
||||||
'original_icon': None,
|
'original_icon': None,
|
||||||
@ -480,7 +474,7 @@
|
|||||||
'supported_features': 0,
|
'supported_features': 0,
|
||||||
'translation_key': 'total_stats_time',
|
'translation_key': 'total_stats_time',
|
||||||
'unique_id': 'E1234567890000000001_total_stats_time',
|
'unique_id': 'E1234567890000000001_total_stats_time',
|
||||||
'unit_of_measurement': <UnitOfTime.HOURS: 'h'>,
|
'unit_of_measurement': <UnitOfTime.MINUTES: 'min'>,
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_sensors[yna5x1-entity_ids0][sensor.ozmo_950_total_time_cleaned:state]
|
# name: test_sensors[yna5x1-entity_ids0][sensor.ozmo_950_total_time_cleaned:state]
|
||||||
|
@ -4,6 +4,7 @@ from __future__ import annotations
|
|||||||
from collections.abc import Generator
|
from collections.abc import Generator
|
||||||
from datetime import UTC, date, datetime
|
from datetime import UTC, date, datetime
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
import logging
|
||||||
from types import ModuleType
|
from types import ModuleType
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
@ -29,6 +30,7 @@ from homeassistant.const import (
|
|||||||
PERCENTAGE,
|
PERCENTAGE,
|
||||||
STATE_UNKNOWN,
|
STATE_UNKNOWN,
|
||||||
EntityCategory,
|
EntityCategory,
|
||||||
|
UnitOfDataRate,
|
||||||
UnitOfEnergy,
|
UnitOfEnergy,
|
||||||
UnitOfLength,
|
UnitOfLength,
|
||||||
UnitOfMass,
|
UnitOfMass,
|
||||||
@ -2604,3 +2606,120 @@ def test_deprecated_constants_sensor_device_class(
|
|||||||
import_and_test_deprecated_constant_enum(
|
import_and_test_deprecated_constant_enum(
|
||||||
caplog, sensor, enum, "DEVICE_CLASS_", "2025.1"
|
caplog, sensor, enum, "DEVICE_CLASS_", "2025.1"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("device_class", "native_unit"),
|
||||||
|
[
|
||||||
|
(SensorDeviceClass.TEMPERATURE, UnitOfTemperature.CELSIUS),
|
||||||
|
(SensorDeviceClass.DATA_RATE, UnitOfDataRate.KILOBITS_PER_SECOND),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_suggested_unit_guard_invalid_unit(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
caplog: pytest.LogCaptureFixture,
|
||||||
|
device_class: SensorDeviceClass,
|
||||||
|
native_unit: str,
|
||||||
|
) -> None:
|
||||||
|
"""Test suggested_unit_of_measurement guard.
|
||||||
|
|
||||||
|
An invalid suggested unit creates a log entry and the suggested unit will be ignored.
|
||||||
|
"""
|
||||||
|
entity_registry = er.async_get(hass)
|
||||||
|
platform = getattr(hass.components, "test.sensor")
|
||||||
|
platform.init(empty=True)
|
||||||
|
|
||||||
|
state_value = 10
|
||||||
|
invalid_suggested_unit = "invalid_unit"
|
||||||
|
|
||||||
|
entity = platform.ENTITIES["0"] = platform.MockSensor(
|
||||||
|
name="Invalid",
|
||||||
|
device_class=device_class,
|
||||||
|
native_unit_of_measurement=native_unit,
|
||||||
|
suggested_unit_of_measurement=invalid_suggested_unit,
|
||||||
|
native_value=str(state_value),
|
||||||
|
unique_id="invalid",
|
||||||
|
)
|
||||||
|
assert await async_setup_component(hass, "sensor", {"sensor": {"platform": "test"}})
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
# Unit of measurement should be native one
|
||||||
|
state = hass.states.get(entity.entity_id)
|
||||||
|
assert int(state.state) == state_value
|
||||||
|
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == native_unit
|
||||||
|
|
||||||
|
# Assert the suggested unit is ignored and not stored in the entity registry
|
||||||
|
entry = entity_registry.async_get(entity.entity_id)
|
||||||
|
assert entry.unit_of_measurement == native_unit
|
||||||
|
assert entry.options == {}
|
||||||
|
assert (
|
||||||
|
"homeassistant.components.sensor",
|
||||||
|
logging.WARNING,
|
||||||
|
(
|
||||||
|
"<class 'custom_components.test.sensor.MockSensor'> sets an"
|
||||||
|
" invalid suggested_unit_of_measurement. Please report it to the author"
|
||||||
|
" of the 'test' custom integration. This warning will become an error in"
|
||||||
|
" Home Assistant Core 2024.5"
|
||||||
|
),
|
||||||
|
) in caplog.record_tuples
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("device_class", "native_unit", "native_value", "suggested_unit", "expect_value"),
|
||||||
|
[
|
||||||
|
(
|
||||||
|
SensorDeviceClass.TEMPERATURE,
|
||||||
|
UnitOfTemperature.CELSIUS,
|
||||||
|
10,
|
||||||
|
UnitOfTemperature.KELVIN,
|
||||||
|
283,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
SensorDeviceClass.DATA_RATE,
|
||||||
|
UnitOfDataRate.KILOBITS_PER_SECOND,
|
||||||
|
10,
|
||||||
|
UnitOfDataRate.BITS_PER_SECOND,
|
||||||
|
10000,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_suggested_unit_guard_valid_unit(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
device_class: SensorDeviceClass,
|
||||||
|
native_unit: str,
|
||||||
|
native_value: int,
|
||||||
|
suggested_unit: str,
|
||||||
|
expect_value: float | int,
|
||||||
|
) -> None:
|
||||||
|
"""Test suggested_unit_of_measurement guard.
|
||||||
|
|
||||||
|
Suggested unit is valid and therefore should be used for unit conversion and stored
|
||||||
|
in the entity registry.
|
||||||
|
"""
|
||||||
|
entity_registry = er.async_get(hass)
|
||||||
|
platform = getattr(hass.components, "test.sensor")
|
||||||
|
platform.init(empty=True)
|
||||||
|
|
||||||
|
entity = platform.ENTITIES["0"] = platform.MockSensor(
|
||||||
|
name="Valid",
|
||||||
|
device_class=device_class,
|
||||||
|
native_unit_of_measurement=native_unit,
|
||||||
|
native_value=str(native_value),
|
||||||
|
suggested_unit_of_measurement=suggested_unit,
|
||||||
|
unique_id="valid",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert await async_setup_component(hass, "sensor", {"sensor": {"platform": "test"}})
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
# Unit of measurement should set to the suggested unit of measurement
|
||||||
|
state = hass.states.get(entity.entity_id)
|
||||||
|
assert float(state.state) == expect_value
|
||||||
|
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == suggested_unit
|
||||||
|
|
||||||
|
# Assert the suggested unit of measurement is stored in the registry
|
||||||
|
entry = entity_registry.async_get(entity.entity_id)
|
||||||
|
assert entry.unit_of_measurement == suggested_unit
|
||||||
|
assert entry.options == {
|
||||||
|
"sensor.private": {"suggested_unit_of_measurement": suggested_unit},
|
||||||
|
}
|
||||||
|
@ -71,9 +71,6 @@
|
|||||||
'id': <ANY>,
|
'id': <ANY>,
|
||||||
'name': None,
|
'name': None,
|
||||||
'options': dict({
|
'options': dict({
|
||||||
'sensor.private': dict({
|
|
||||||
'suggested_unit_of_measurement': <UnitOfTime.HOURS: 'h'>,
|
|
||||||
}),
|
|
||||||
}),
|
}),
|
||||||
'original_device_class': <SensorDeviceClass.DURATION: 'duration'>,
|
'original_device_class': <SensorDeviceClass.DURATION: 'duration'>,
|
||||||
'original_icon': None,
|
'original_icon': None,
|
||||||
@ -83,7 +80,7 @@
|
|||||||
'supported_features': 0,
|
'supported_features': 0,
|
||||||
'translation_key': 'activity_active_duration_today',
|
'translation_key': 'activity_active_duration_today',
|
||||||
'unique_id': 'withings_12345_activity_active_duration_today',
|
'unique_id': 'withings_12345_activity_active_duration_today',
|
||||||
'unit_of_measurement': <UnitOfTime.HOURS: 'h'>,
|
'unit_of_measurement': <UnitOfTime.SECONDS: 's'>,
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_all_entities[sensor.henk_active_time_today-state]
|
# name: test_all_entities[sensor.henk_active_time_today-state]
|
||||||
@ -1176,9 +1173,6 @@
|
|||||||
'id': <ANY>,
|
'id': <ANY>,
|
||||||
'name': None,
|
'name': None,
|
||||||
'options': dict({
|
'options': dict({
|
||||||
'sensor.private': dict({
|
|
||||||
'suggested_unit_of_measurement': <UnitOfTime.MINUTES: 'min'>,
|
|
||||||
}),
|
|
||||||
}),
|
}),
|
||||||
'original_device_class': <SensorDeviceClass.DURATION: 'duration'>,
|
'original_device_class': <SensorDeviceClass.DURATION: 'duration'>,
|
||||||
'original_icon': None,
|
'original_icon': None,
|
||||||
@ -1188,7 +1182,7 @@
|
|||||||
'supported_features': 0,
|
'supported_features': 0,
|
||||||
'translation_key': 'activity_intense_duration_today',
|
'translation_key': 'activity_intense_duration_today',
|
||||||
'unique_id': 'withings_12345_activity_intense_duration_today',
|
'unique_id': 'withings_12345_activity_intense_duration_today',
|
||||||
'unit_of_measurement': <UnitOfTime.MINUTES: 'min'>,
|
'unit_of_measurement': <UnitOfTime.SECONDS: 's'>,
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_all_entities[sensor.henk_intense_activity_today-state]
|
# name: test_all_entities[sensor.henk_intense_activity_today-state]
|
||||||
@ -1274,9 +1268,6 @@
|
|||||||
'id': <ANY>,
|
'id': <ANY>,
|
||||||
'name': None,
|
'name': None,
|
||||||
'options': dict({
|
'options': dict({
|
||||||
'sensor.private': dict({
|
|
||||||
'suggested_unit_of_measurement': <UnitOfTime.MINUTES: 'min'>,
|
|
||||||
}),
|
|
||||||
}),
|
}),
|
||||||
'original_device_class': <SensorDeviceClass.DURATION: 'duration'>,
|
'original_device_class': <SensorDeviceClass.DURATION: 'duration'>,
|
||||||
'original_icon': None,
|
'original_icon': None,
|
||||||
@ -1286,7 +1277,7 @@
|
|||||||
'supported_features': 0,
|
'supported_features': 0,
|
||||||
'translation_key': 'workout_duration',
|
'translation_key': 'workout_duration',
|
||||||
'unique_id': 'withings_12345_workout_duration',
|
'unique_id': 'withings_12345_workout_duration',
|
||||||
'unit_of_measurement': <UnitOfTime.MINUTES: 'min'>,
|
'unit_of_measurement': <UnitOfTime.SECONDS: 's'>,
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_all_entities[sensor.henk_last_workout_duration-state]
|
# name: test_all_entities[sensor.henk_last_workout_duration-state]
|
||||||
@ -1750,9 +1741,6 @@
|
|||||||
'id': <ANY>,
|
'id': <ANY>,
|
||||||
'name': None,
|
'name': None,
|
||||||
'options': dict({
|
'options': dict({
|
||||||
'sensor.private': dict({
|
|
||||||
'suggested_unit_of_measurement': <UnitOfTime.MINUTES: 'min'>,
|
|
||||||
}),
|
|
||||||
}),
|
}),
|
||||||
'original_device_class': <SensorDeviceClass.DURATION: 'duration'>,
|
'original_device_class': <SensorDeviceClass.DURATION: 'duration'>,
|
||||||
'original_icon': None,
|
'original_icon': None,
|
||||||
@ -1762,7 +1750,7 @@
|
|||||||
'supported_features': 0,
|
'supported_features': 0,
|
||||||
'translation_key': 'activity_moderate_duration_today',
|
'translation_key': 'activity_moderate_duration_today',
|
||||||
'unique_id': 'withings_12345_activity_moderate_duration_today',
|
'unique_id': 'withings_12345_activity_moderate_duration_today',
|
||||||
'unit_of_measurement': <UnitOfTime.MINUTES: 'min'>,
|
'unit_of_measurement': <UnitOfTime.SECONDS: 's'>,
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_all_entities[sensor.henk_moderate_activity_today-state]
|
# name: test_all_entities[sensor.henk_moderate_activity_today-state]
|
||||||
@ -1851,9 +1839,6 @@
|
|||||||
'id': <ANY>,
|
'id': <ANY>,
|
||||||
'name': None,
|
'name': None,
|
||||||
'options': dict({
|
'options': dict({
|
||||||
'sensor.private': dict({
|
|
||||||
'suggested_unit_of_measurement': <UnitOfTime.MINUTES: 'min'>,
|
|
||||||
}),
|
|
||||||
}),
|
}),
|
||||||
'original_device_class': <SensorDeviceClass.DURATION: 'duration'>,
|
'original_device_class': <SensorDeviceClass.DURATION: 'duration'>,
|
||||||
'original_icon': None,
|
'original_icon': None,
|
||||||
@ -1863,7 +1848,7 @@
|
|||||||
'supported_features': 0,
|
'supported_features': 0,
|
||||||
'translation_key': 'workout_pause_duration',
|
'translation_key': 'workout_pause_duration',
|
||||||
'unique_id': 'withings_12345_workout_pause_duration',
|
'unique_id': 'withings_12345_workout_pause_duration',
|
||||||
'unit_of_measurement': <UnitOfTime.MINUTES: 'min'>,
|
'unit_of_measurement': <UnitOfTime.SECONDS: 's'>,
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_all_entities[sensor.henk_pause_during_last_workout-state]
|
# name: test_all_entities[sensor.henk_pause_during_last_workout-state]
|
||||||
@ -2045,9 +2030,6 @@
|
|||||||
'id': <ANY>,
|
'id': <ANY>,
|
||||||
'name': None,
|
'name': None,
|
||||||
'options': dict({
|
'options': dict({
|
||||||
'sensor.private': dict({
|
|
||||||
'suggested_unit_of_measurement': <UnitOfTime.HOURS: 'h'>,
|
|
||||||
}),
|
|
||||||
}),
|
}),
|
||||||
'original_device_class': <SensorDeviceClass.DURATION: 'duration'>,
|
'original_device_class': <SensorDeviceClass.DURATION: 'duration'>,
|
||||||
'original_icon': None,
|
'original_icon': None,
|
||||||
@ -2057,7 +2039,7 @@
|
|||||||
'supported_features': 0,
|
'supported_features': 0,
|
||||||
'translation_key': 'sleep_goal',
|
'translation_key': 'sleep_goal',
|
||||||
'unique_id': 'withings_12345_sleep_goal',
|
'unique_id': 'withings_12345_sleep_goal',
|
||||||
'unit_of_measurement': <UnitOfTime.HOURS: 'h'>,
|
'unit_of_measurement': <UnitOfTime.SECONDS: 's'>,
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_all_entities[sensor.henk_sleep_goal-state]
|
# name: test_all_entities[sensor.henk_sleep_goal-state]
|
||||||
@ -2235,9 +2217,6 @@
|
|||||||
'id': <ANY>,
|
'id': <ANY>,
|
||||||
'name': None,
|
'name': None,
|
||||||
'options': dict({
|
'options': dict({
|
||||||
'sensor.private': dict({
|
|
||||||
'suggested_unit_of_measurement': <UnitOfTime.MINUTES: 'min'>,
|
|
||||||
}),
|
|
||||||
}),
|
}),
|
||||||
'original_device_class': <SensorDeviceClass.DURATION: 'duration'>,
|
'original_device_class': <SensorDeviceClass.DURATION: 'duration'>,
|
||||||
'original_icon': None,
|
'original_icon': None,
|
||||||
@ -2247,7 +2226,7 @@
|
|||||||
'supported_features': 0,
|
'supported_features': 0,
|
||||||
'translation_key': 'activity_soft_duration_today',
|
'translation_key': 'activity_soft_duration_today',
|
||||||
'unique_id': 'withings_12345_activity_soft_duration_today',
|
'unique_id': 'withings_12345_activity_soft_duration_today',
|
||||||
'unit_of_measurement': <UnitOfTime.MINUTES: 'min'>,
|
'unit_of_measurement': <UnitOfTime.SECONDS: 's'>,
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_all_entities[sensor.henk_soft_activity_today-state]
|
# name: test_all_entities[sensor.henk_soft_activity_today-state]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user