mirror of
https://github.com/home-assistant/core.git
synced 2026-08-24 02:24:51 -05:00
Remove deprecated battery properties from vacuum (#175682)
This commit is contained in:
@@ -73,7 +73,6 @@ from homeassistant.components.water_heater import (
|
||||
)
|
||||
from homeassistant.const import (
|
||||
ATTR_ASSUMED_STATE,
|
||||
ATTR_BATTERY_LEVEL,
|
||||
ATTR_CODE,
|
||||
ATTR_DEVICE_CLASS,
|
||||
ATTR_ENTITY_ID,
|
||||
@@ -848,65 +847,6 @@ class LocatorTrait(_Trait):
|
||||
)
|
||||
|
||||
|
||||
@register_trait
|
||||
class EnergyStorageTrait(_Trait):
|
||||
"""Trait to offer EnergyStorage functionality.
|
||||
|
||||
https://developers.google.com/actions/smarthome/traits/energystorage
|
||||
"""
|
||||
|
||||
name = TRAIT_ENERGY_STORAGE
|
||||
commands = [COMMAND_CHARGE]
|
||||
|
||||
@staticmethod
|
||||
@override
|
||||
def supported(domain, features, device_class, _):
|
||||
"""Test if state is supported."""
|
||||
return domain == VACUUM_DOMAIN and features & VacuumEntityFeature.BATTERY
|
||||
|
||||
@override
|
||||
def sync_attributes(self) -> dict[str, Any]:
|
||||
"""Return EnergyStorage attributes for a sync request."""
|
||||
return {
|
||||
"isRechargeable": True,
|
||||
"queryOnlyEnergyStorage": True,
|
||||
}
|
||||
|
||||
@override
|
||||
def query_attributes(self) -> dict[str, Any]:
|
||||
"""Return EnergyStorage query attributes."""
|
||||
battery_level = self.state.attributes.get(ATTR_BATTERY_LEVEL)
|
||||
if battery_level is None:
|
||||
return {}
|
||||
if battery_level == 100:
|
||||
descriptive_capacity_remaining = "FULL"
|
||||
elif 75 <= battery_level < 100:
|
||||
descriptive_capacity_remaining = "HIGH"
|
||||
elif 50 <= battery_level < 75:
|
||||
descriptive_capacity_remaining = "MEDIUM"
|
||||
elif 25 <= battery_level < 50:
|
||||
descriptive_capacity_remaining = "LOW"
|
||||
elif 0 <= battery_level < 25:
|
||||
descriptive_capacity_remaining = "CRITICALLY_LOW"
|
||||
return {
|
||||
"descriptiveCapacityRemaining": descriptive_capacity_remaining,
|
||||
"capacityRemaining": [{"rawValue": battery_level, "unit": "PERCENTAGE"}],
|
||||
"capacityUntilFull": [
|
||||
{"rawValue": 100 - battery_level, "unit": "PERCENTAGE"}
|
||||
],
|
||||
"isCharging": self.state.state == vacuum.VacuumActivity.DOCKED,
|
||||
"isPluggedIn": self.state.state == vacuum.VacuumActivity.DOCKED,
|
||||
}
|
||||
|
||||
@override
|
||||
async def execute(self, command, data, params, challenge):
|
||||
"""Execute a dock command."""
|
||||
raise SmartHomeError(
|
||||
ERR_FUNCTION_NOT_SUPPORTED,
|
||||
"Controlling charging of a vacuum is not yet supported",
|
||||
)
|
||||
|
||||
|
||||
@register_trait
|
||||
class StartStopTrait(_Trait):
|
||||
"""Trait to offer StartStop functionality.
|
||||
@@ -1908,7 +1848,7 @@ class FanSpeedTrait(_Trait):
|
||||
name = TRAIT_FAN_SPEED
|
||||
commands = [COMMAND_SET_FAN_SPEED, COMMAND_REVERSE]
|
||||
|
||||
def __init__(self, hass, state, config):
|
||||
def __init__(self, hass: HomeAssistant, state, config) -> None:
|
||||
"""Initialize a trait for a state."""
|
||||
super().__init__(hass, state, config)
|
||||
if state.domain == FAN_DOMAIN:
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
"""Support for vacuum cleaner robots (botvacs)."""
|
||||
|
||||
import asyncio
|
||||
from collections.abc import Mapping
|
||||
from dataclasses import dataclass
|
||||
from datetime import timedelta
|
||||
@@ -13,7 +12,6 @@ import voluptuous as vol
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import ( # noqa: F401 # STATE_PAUSED/IDLE are API
|
||||
ATTR_BATTERY_LEVEL,
|
||||
ATTR_COMMAND,
|
||||
SERVICE_TOGGLE,
|
||||
SERVICE_TURN_OFF,
|
||||
@@ -29,9 +27,6 @@ from homeassistant.helpers import (
|
||||
)
|
||||
from homeassistant.helpers.entity import Entity, EntityDescription
|
||||
from homeassistant.helpers.entity_component import EntityComponent
|
||||
from homeassistant.helpers.entity_platform import EntityPlatform
|
||||
from homeassistant.helpers.frame import ReportBehavior, report_usage
|
||||
from homeassistant.helpers.icon import icon_for_battery_level
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
from .const import (
|
||||
@@ -51,7 +46,6 @@ PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA
|
||||
PLATFORM_SCHEMA_BASE = cv.PLATFORM_SCHEMA_BASE
|
||||
SCAN_INTERVAL = timedelta(seconds=20)
|
||||
|
||||
ATTR_BATTERY_ICON = "battery_icon"
|
||||
ATTR_CLEANED_AREA = "cleaned_area"
|
||||
ATTR_FAN_SPEED = "fan_speed"
|
||||
ATTR_FAN_SPEED_LIST = "fan_speed_list"
|
||||
@@ -73,8 +67,6 @@ DEFAULT_NAME = "Vacuum cleaner robot"
|
||||
|
||||
ISSUE_SEGMENTS_CHANGED = "segments_changed"
|
||||
|
||||
_BATTERY_DEPRECATION_IGNORED_PLATFORMS = ("template",)
|
||||
|
||||
|
||||
# mypy: disallow-any-generics
|
||||
|
||||
@@ -173,8 +165,6 @@ class StateVacuumEntityDescription(EntityDescription, frozen_or_thawed=True):
|
||||
|
||||
STATE_VACUUM_CACHED_PROPERTIES_WITH_ATTR_ = {
|
||||
"supported_features",
|
||||
"battery_level",
|
||||
"battery_icon",
|
||||
"fan_speed",
|
||||
"fan_speed_list",
|
||||
"activity",
|
||||
@@ -192,8 +182,6 @@ class StateVacuumEntity(
|
||||
{VacuumEntityCapabilityAttribute.FAN_SPEED_LIST}
|
||||
)
|
||||
|
||||
_attr_battery_icon: str
|
||||
_attr_battery_level: int | None = None
|
||||
_attr_fan_speed: str | None = None
|
||||
_attr_fan_speed_list: list[str]
|
||||
_attr_activity: VacuumActivity | None = None
|
||||
@@ -202,121 +190,12 @@ class StateVacuumEntity(
|
||||
_segments_not_configured_issue_created: bool = False
|
||||
_segments_changed_last_seen: list[dict[str, Any]] | None = None
|
||||
|
||||
__vacuum_legacy_battery_level: bool = False
|
||||
__vacuum_legacy_battery_icon: bool = False
|
||||
__vacuum_legacy_battery_feature: bool = False
|
||||
|
||||
@override
|
||||
def __init_subclass__(cls, **kwargs: Any) -> None:
|
||||
"""Post initialisation processing."""
|
||||
super().__init_subclass__(**kwargs)
|
||||
if any(
|
||||
method in cls.__dict__
|
||||
for method in ("_attr_battery_level", "battery_level")
|
||||
):
|
||||
# Integrations should use a separate battery sensor.
|
||||
cls.__vacuum_legacy_battery_level = True
|
||||
if any(
|
||||
method in cls.__dict__ for method in ("_attr_battery_icon", "battery_icon")
|
||||
):
|
||||
# Integrations should use a separate battery sensor.
|
||||
cls.__vacuum_legacy_battery_icon = True
|
||||
|
||||
@override
|
||||
def __setattr__(self, name: str, value: Any) -> None:
|
||||
"""Set attribute.
|
||||
|
||||
Deprecation warning if setting battery icon or battery level
|
||||
attributes directly unless already reported.
|
||||
"""
|
||||
if name in {"_attr_battery_level", "_attr_battery_icon"}:
|
||||
self._report_deprecated_battery_properties(name[6:])
|
||||
return super().__setattr__(name, value)
|
||||
|
||||
@callback
|
||||
@override
|
||||
def add_to_platform_start(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
platform: EntityPlatform,
|
||||
parallel_updates: asyncio.Semaphore | None,
|
||||
) -> None:
|
||||
"""Start adding an entity to a platform."""
|
||||
super().add_to_platform_start(hass, platform, parallel_updates)
|
||||
if self.__vacuum_legacy_battery_level:
|
||||
self._report_deprecated_battery_properties("battery_level")
|
||||
if self.__vacuum_legacy_battery_icon:
|
||||
self._report_deprecated_battery_properties("battery_icon")
|
||||
|
||||
@callback
|
||||
@override
|
||||
def async_registry_entry_updated(self) -> None:
|
||||
"""Run when the entity registry entry has been updated."""
|
||||
self._async_check_segments_issues()
|
||||
|
||||
@callback
|
||||
def _report_deprecated_battery_properties(self, property: str) -> None:
|
||||
"""Report on deprecated use of battery properties.
|
||||
|
||||
Integrations should implement a sensor instead.
|
||||
"""
|
||||
if (
|
||||
self.platform
|
||||
and self.platform.platform_name
|
||||
not in _BATTERY_DEPRECATION_IGNORED_PLATFORMS
|
||||
):
|
||||
# Don't report usage until after entity added to hass, after init
|
||||
report_usage(
|
||||
f"is setting the {property} which has been deprecated."
|
||||
f" Integration {self.platform.platform_name} should implement a sensor"
|
||||
" instead with a correct device class and link it to the same device",
|
||||
core_integration_behavior=ReportBehavior.IGNORE,
|
||||
custom_integration_behavior=ReportBehavior.LOG,
|
||||
breaks_in_ha_version="2026.8",
|
||||
integration_domain=self.platform.platform_name,
|
||||
exclude_integrations={DOMAIN},
|
||||
)
|
||||
|
||||
@callback
|
||||
def _report_deprecated_battery_feature(self) -> None:
|
||||
"""Report on deprecated use of battery supported features.
|
||||
|
||||
Integrations should remove the battery supported feature when migrating
|
||||
battery level and icon to a sensor.
|
||||
"""
|
||||
if (
|
||||
self.platform
|
||||
and self.platform.platform_name
|
||||
not in _BATTERY_DEPRECATION_IGNORED_PLATFORMS
|
||||
):
|
||||
# Don't report usage until after entity added to hass, after init
|
||||
report_usage(
|
||||
f"is setting the battery supported feature which has been deprecated."
|
||||
f" Integration {self.platform.platform_name}"
|
||||
" should remove this as part of migrating"
|
||||
" the battery level and icon to a sensor",
|
||||
core_behavior=ReportBehavior.LOG,
|
||||
core_integration_behavior=ReportBehavior.IGNORE,
|
||||
custom_integration_behavior=ReportBehavior.LOG,
|
||||
breaks_in_ha_version="2026.8",
|
||||
integration_domain=self.platform.platform_name,
|
||||
exclude_integrations={DOMAIN},
|
||||
)
|
||||
|
||||
@cached_property
|
||||
def battery_level(self) -> int | None:
|
||||
"""Return the battery level of the vacuum cleaner."""
|
||||
return self._attr_battery_level
|
||||
|
||||
@property
|
||||
def battery_icon(self) -> str:
|
||||
"""Return the battery icon for the vacuum cleaner."""
|
||||
charging = bool(self.activity == VacuumActivity.DOCKED)
|
||||
|
||||
return icon_for_battery_level(
|
||||
battery_level=self.battery_level, charging=charging
|
||||
)
|
||||
|
||||
@property
|
||||
@override
|
||||
def capability_attributes(self) -> dict[str, Any] | None:
|
||||
@@ -342,13 +221,6 @@ class StateVacuumEntity(
|
||||
data: dict[str, Any] = {}
|
||||
supported_features = self.supported_features
|
||||
|
||||
if VacuumEntityFeature.BATTERY in supported_features:
|
||||
if self.__vacuum_legacy_battery_feature is False:
|
||||
self._report_deprecated_battery_feature()
|
||||
self.__vacuum_legacy_battery_feature = True
|
||||
data[ATTR_BATTERY_LEVEL] = self.battery_level
|
||||
data[ATTR_BATTERY_ICON] = self.battery_icon
|
||||
|
||||
if VacuumEntityFeature.FAN_SPEED in supported_features:
|
||||
data[VacuumEntityStateAttribute.FAN_SPEED] = self.fan_speed
|
||||
|
||||
|
||||
@@ -46,7 +46,6 @@ class VacuumEntityFeature(IntFlag):
|
||||
STOP = 8
|
||||
RETURN_HOME = 16
|
||||
FAN_SPEED = 32
|
||||
BATTERY = 64
|
||||
STATUS = 128 # Deprecated, not supported by StateVacuumEntity
|
||||
SEND_COMMAND = 256
|
||||
LOCATE = 512
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
"""Helper to test significant Vacuum state changes."""
|
||||
|
||||
from typing import Any
|
||||
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.significant_change import (
|
||||
check_absolute_change,
|
||||
check_valid_float,
|
||||
)
|
||||
|
||||
from . import ATTR_BATTERY_LEVEL
|
||||
from .const import VacuumEntityStateAttribute
|
||||
|
||||
SIGNIFICANT_ATTRIBUTES: set[str] = {
|
||||
ATTR_BATTERY_LEVEL,
|
||||
VacuumEntityStateAttribute.FAN_SPEED,
|
||||
}
|
||||
|
||||
|
||||
@callback
|
||||
def async_check_significant_change(
|
||||
hass: HomeAssistant,
|
||||
old_state: str,
|
||||
old_attrs: dict,
|
||||
new_state: str,
|
||||
new_attrs: dict,
|
||||
**kwargs: Any,
|
||||
) -> bool | None:
|
||||
"""Test if state significantly changed."""
|
||||
if old_state != new_state:
|
||||
return True
|
||||
|
||||
old_attrs_s = set(
|
||||
{k: v for k, v in old_attrs.items() if k in SIGNIFICANT_ATTRIBUTES}.items()
|
||||
)
|
||||
new_attrs_s = set(
|
||||
{k: v for k, v in new_attrs.items() if k in SIGNIFICANT_ATTRIBUTES}.items()
|
||||
)
|
||||
changed_attrs: set[str] = {item[0] for item in old_attrs_s ^ new_attrs_s}
|
||||
|
||||
for attr_name in changed_attrs:
|
||||
if attr_name != ATTR_BATTERY_LEVEL:
|
||||
return True
|
||||
|
||||
old_attr_value = old_attrs.get(attr_name)
|
||||
new_attr_value = new_attrs.get(attr_name)
|
||||
if new_attr_value is None or not check_valid_float(new_attr_value):
|
||||
# New attribute value is invalid, ignore it
|
||||
continue
|
||||
|
||||
if old_attr_value is None or not check_valid_float(old_attr_value):
|
||||
# Old attribute value was invalid, we should report again
|
||||
return True
|
||||
|
||||
if check_absolute_change(old_attr_value, new_attr_value, 1.0):
|
||||
return True
|
||||
|
||||
# no significant attribute change detected
|
||||
return False
|
||||
@@ -58,7 +58,6 @@ from homeassistant.components.valve import ValveEntityFeature
|
||||
from homeassistant.components.water_heater import WaterHeaterEntityFeature
|
||||
from homeassistant.const import (
|
||||
ATTR_ASSUMED_STATE,
|
||||
ATTR_BATTERY_LEVEL,
|
||||
ATTR_DEVICE_CLASS,
|
||||
ATTR_ENTITY_ID,
|
||||
ATTR_MODE,
|
||||
@@ -484,74 +483,6 @@ async def test_locate_vacuum(hass: HomeAssistant) -> None:
|
||||
assert err.value.code == const.ERR_FUNCTION_NOT_SUPPORTED
|
||||
|
||||
|
||||
async def test_energystorage_vacuum(hass: HomeAssistant) -> None:
|
||||
"""Test EnergyStorage trait support for vacuum domain."""
|
||||
assert helpers.get_google_type(vacuum.DOMAIN, None) is not None
|
||||
assert trait.EnergyStorageTrait.supported(
|
||||
vacuum.DOMAIN, VacuumEntityFeature.BATTERY, None, None
|
||||
)
|
||||
|
||||
trt = trait.EnergyStorageTrait(
|
||||
hass,
|
||||
State(
|
||||
"vacuum.bla",
|
||||
vacuum.VacuumActivity.DOCKED,
|
||||
{
|
||||
ATTR_SUPPORTED_FEATURES: VacuumEntityFeature.BATTERY,
|
||||
ATTR_BATTERY_LEVEL: 100,
|
||||
},
|
||||
),
|
||||
BASIC_CONFIG,
|
||||
)
|
||||
|
||||
assert trt.sync_attributes() == {
|
||||
"isRechargeable": True,
|
||||
"queryOnlyEnergyStorage": True,
|
||||
}
|
||||
|
||||
assert trt.query_attributes() == {
|
||||
"descriptiveCapacityRemaining": "FULL",
|
||||
"capacityRemaining": [{"rawValue": 100, "unit": "PERCENTAGE"}],
|
||||
"capacityUntilFull": [{"rawValue": 0, "unit": "PERCENTAGE"}],
|
||||
"isCharging": True,
|
||||
"isPluggedIn": True,
|
||||
}
|
||||
|
||||
trt = trait.EnergyStorageTrait(
|
||||
hass,
|
||||
State(
|
||||
"vacuum.bla",
|
||||
vacuum.VacuumActivity.CLEANING,
|
||||
{
|
||||
ATTR_SUPPORTED_FEATURES: VacuumEntityFeature.BATTERY,
|
||||
ATTR_BATTERY_LEVEL: 20,
|
||||
},
|
||||
),
|
||||
BASIC_CONFIG,
|
||||
)
|
||||
|
||||
assert trt.sync_attributes() == {
|
||||
"isRechargeable": True,
|
||||
"queryOnlyEnergyStorage": True,
|
||||
}
|
||||
|
||||
assert trt.query_attributes() == {
|
||||
"descriptiveCapacityRemaining": "CRITICALLY_LOW",
|
||||
"capacityRemaining": [{"rawValue": 20, "unit": "PERCENTAGE"}],
|
||||
"capacityUntilFull": [{"rawValue": 80, "unit": "PERCENTAGE"}],
|
||||
"isCharging": False,
|
||||
"isPluggedIn": False,
|
||||
}
|
||||
|
||||
with pytest.raises(helpers.SmartHomeError) as err:
|
||||
await trt.execute(trait.COMMAND_CHARGE, BASIC_DATA, {"charge": True}, {})
|
||||
assert err.value.code == const.ERR_FUNCTION_NOT_SUPPORTED
|
||||
|
||||
with pytest.raises(helpers.SmartHomeError) as err:
|
||||
await trt.execute(trait.COMMAND_CHARGE, BASIC_DATA, {"charge": False}, {})
|
||||
assert err.value.code == const.ERR_FUNCTION_NOT_SUPPORTED
|
||||
|
||||
|
||||
async def test_startstop_vacuum(
|
||||
hass: HomeAssistant,
|
||||
entity_registry: er.EntityRegistry,
|
||||
|
||||
@@ -705,8 +705,7 @@ async def test_status(
|
||||
(
|
||||
{
|
||||
mqttvacuum.CONF_SUPPORTED_FEATURES: services_to_strings(
|
||||
mqttvacuum.DEFAULT_SERVICES
|
||||
| vacuum.VacuumEntityFeature.BATTERY,
|
||||
mqttvacuum.DEFAULT_SERVICES,
|
||||
SERVICE_TO_STRING,
|
||||
)
|
||||
},
|
||||
|
||||
@@ -23,13 +23,11 @@ class MockVacuum(MockEntity, StateVacuumEntity):
|
||||
| VacuumEntityFeature.STOP
|
||||
| VacuumEntityFeature.RETURN_HOME
|
||||
| VacuumEntityFeature.FAN_SPEED
|
||||
| VacuumEntityFeature.BATTERY
|
||||
| VacuumEntityFeature.CLEAN_SPOT
|
||||
| VacuumEntityFeature.MAP
|
||||
| VacuumEntityFeature.STATE
|
||||
| VacuumEntityFeature.START
|
||||
)
|
||||
_attr_battery_level = 99
|
||||
_attr_fan_speed_list = ["slow", "fast"]
|
||||
|
||||
def __init__(self, **values: Any) -> None:
|
||||
|
||||
@@ -47,7 +47,6 @@ async def vacuum_supported_features() -> VacuumEntityFeature:
|
||||
| VacuumEntityFeature.STOP
|
||||
| VacuumEntityFeature.RETURN_HOME
|
||||
| VacuumEntityFeature.FAN_SPEED
|
||||
| VacuumEntityFeature.BATTERY
|
||||
| VacuumEntityFeature.CLEAN_SPOT
|
||||
| VacuumEntityFeature.MAP
|
||||
| VacuumEntityFeature.STATE
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
"""The tests for the Vacuum entity integration."""
|
||||
|
||||
from dataclasses import asdict
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
@@ -31,7 +30,6 @@ from . import (
|
||||
help_async_setup_entry_init,
|
||||
help_async_unload_entry,
|
||||
)
|
||||
from .common import async_start
|
||||
|
||||
from tests.common import (
|
||||
MockConfigEntry,
|
||||
@@ -552,214 +550,3 @@ async def test_segments_changed_issue(
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert ir.async_get(hass).async_get_issue(DOMAIN, issue_id) is None # pylint: disable=home-assistant-tests-registry-fixtures
|
||||
|
||||
|
||||
@pytest.mark.parametrize(("is_built_in", "log_warnings"), [(True, 0), (False, 3)])
|
||||
async def test_vacuum_log_deprecated_battery_using_properties(
|
||||
hass: HomeAssistant,
|
||||
config_flow_fixture: None,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
is_built_in: bool,
|
||||
log_warnings: int,
|
||||
) -> None:
|
||||
"""Test incorrectly using battery properties logs warning."""
|
||||
|
||||
class MockLegacyVacuum(MockVacuum):
|
||||
"""Mocked vacuum entity."""
|
||||
|
||||
@property
|
||||
def activity(self) -> VacuumActivity:
|
||||
"""Return the state of the entity."""
|
||||
return VacuumActivity.CLEANING
|
||||
|
||||
@property
|
||||
def battery_level(self) -> int:
|
||||
"""Return the battery level of the vacuum."""
|
||||
return 50
|
||||
|
||||
@property
|
||||
def battery_icon(self) -> str:
|
||||
"""Return the battery icon of the vacuum."""
|
||||
return "mdi:battery-50"
|
||||
|
||||
entity = MockLegacyVacuum(
|
||||
name="Testing",
|
||||
entity_id="vacuum.test",
|
||||
)
|
||||
config_entry = MockConfigEntry(domain="test")
|
||||
config_entry.add_to_hass(hass)
|
||||
|
||||
mock_integration(
|
||||
hass,
|
||||
MockModule(
|
||||
"test",
|
||||
async_setup_entry=help_async_setup_entry_init,
|
||||
async_unload_entry=help_async_unload_entry,
|
||||
),
|
||||
built_in=is_built_in,
|
||||
)
|
||||
setup_test_component_platform(hass, DOMAIN, [entity], from_config_entry=True)
|
||||
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
|
||||
state = hass.states.get(entity.entity_id)
|
||||
assert state is not None
|
||||
|
||||
assert (
|
||||
len([record for record in caplog.records if record.levelno >= logging.WARNING])
|
||||
== log_warnings
|
||||
)
|
||||
|
||||
assert (
|
||||
"integration 'test' is setting the battery_icon which has been deprecated."
|
||||
in caplog.text
|
||||
) != is_built_in
|
||||
assert (
|
||||
"integration 'test' is setting the battery_level which has been deprecated."
|
||||
in caplog.text
|
||||
) != is_built_in
|
||||
|
||||
|
||||
@pytest.mark.parametrize(("is_built_in", "log_warnings"), [(True, 0), (False, 3)])
|
||||
async def test_vacuum_log_deprecated_battery_using_attr(
|
||||
hass: HomeAssistant,
|
||||
config_flow_fixture: None,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
is_built_in: bool,
|
||||
log_warnings: int,
|
||||
) -> None:
|
||||
"""Test _attr_battery_* attribute logs issue and raises repair."""
|
||||
|
||||
class MockLegacyVacuum(MockVacuum):
|
||||
"""Mocked vacuum entity."""
|
||||
|
||||
def start(self) -> None:
|
||||
"""Start cleaning."""
|
||||
self._attr_battery_level = 50
|
||||
self._attr_battery_icon = "mdi:battery-50"
|
||||
|
||||
entity = MockLegacyVacuum(
|
||||
name="Testing",
|
||||
entity_id="vacuum.test",
|
||||
)
|
||||
config_entry = MockConfigEntry(domain="test")
|
||||
config_entry.add_to_hass(hass)
|
||||
|
||||
mock_integration(
|
||||
hass,
|
||||
MockModule(
|
||||
"test",
|
||||
async_setup_entry=help_async_setup_entry_init,
|
||||
async_unload_entry=help_async_unload_entry,
|
||||
),
|
||||
built_in=is_built_in,
|
||||
)
|
||||
setup_test_component_platform(hass, DOMAIN, [entity], from_config_entry=True)
|
||||
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
|
||||
state = hass.states.get(entity.entity_id)
|
||||
assert state is not None
|
||||
entity.start()
|
||||
|
||||
assert (
|
||||
len([record for record in caplog.records if record.levelno >= logging.WARNING])
|
||||
== log_warnings
|
||||
)
|
||||
|
||||
assert (
|
||||
"integration 'test' is setting the battery_level which has been deprecated."
|
||||
in caplog.text
|
||||
) != is_built_in
|
||||
assert (
|
||||
"integration 'test' is setting the battery_icon which has been deprecated."
|
||||
in caplog.text
|
||||
) != is_built_in
|
||||
|
||||
await async_start(hass, entity.entity_id)
|
||||
|
||||
caplog.clear()
|
||||
|
||||
await async_start(hass, entity.entity_id)
|
||||
|
||||
# Test we only log once
|
||||
assert (
|
||||
len([record for record in caplog.records if record.levelno >= logging.WARNING])
|
||||
== 0
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(("is_built_in", "log_warnings"), [(True, 0), (False, 1)])
|
||||
async def test_vacuum_log_deprecated_battery_supported_feature(
|
||||
hass: HomeAssistant,
|
||||
config_flow_fixture: None,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
is_built_in: bool,
|
||||
log_warnings: int,
|
||||
) -> None:
|
||||
"""Test incorrectly setting battery supported feature logs warning."""
|
||||
|
||||
class MockVacuum(StateVacuumEntity):
|
||||
"""Mock vacuum class."""
|
||||
|
||||
_attr_supported_features = (
|
||||
VacuumEntityFeature.STATE | VacuumEntityFeature.BATTERY
|
||||
)
|
||||
_attr_name = "Testing"
|
||||
|
||||
entity = MockVacuum()
|
||||
config_entry = MockConfigEntry(domain="test")
|
||||
config_entry.add_to_hass(hass)
|
||||
|
||||
mock_integration(
|
||||
hass,
|
||||
MockModule(
|
||||
"test",
|
||||
async_setup_entry=help_async_setup_entry_init,
|
||||
async_unload_entry=help_async_unload_entry,
|
||||
),
|
||||
built_in=is_built_in,
|
||||
)
|
||||
setup_test_component_platform(hass, DOMAIN, [entity], from_config_entry=True)
|
||||
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
|
||||
state = hass.states.get(entity.entity_id)
|
||||
assert state is not None
|
||||
|
||||
assert (
|
||||
len([record for record in caplog.records if record.levelno >= logging.WARNING])
|
||||
== log_warnings
|
||||
)
|
||||
|
||||
assert (
|
||||
"integration 'test' is setting the battery supported feature" in caplog.text
|
||||
) != is_built_in
|
||||
|
||||
|
||||
async def test_vacuum_not_log_deprecated_battery_properties_during_init(
|
||||
hass: HomeAssistant,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
"""Test not logging deprecation until after added to hass."""
|
||||
|
||||
class MockLegacyVacuum(MockVacuum):
|
||||
"""Mocked vacuum entity."""
|
||||
|
||||
def __init__(self, **kwargs: Any) -> None:
|
||||
"""Initialize a mock vacuum entity."""
|
||||
super().__init__(**kwargs)
|
||||
self._attr_battery_level = 50
|
||||
|
||||
@property
|
||||
def activity(self) -> VacuumActivity:
|
||||
"""Return the state of the entity."""
|
||||
return VacuumActivity.CLEANING
|
||||
|
||||
entity = MockLegacyVacuum(
|
||||
name="Testing",
|
||||
entity_id="vacuum.test",
|
||||
)
|
||||
assert entity.battery_level == 50
|
||||
|
||||
assert (
|
||||
len([record for record in caplog.records if record.levelno >= logging.WARNING])
|
||||
== 0
|
||||
)
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
"""Test the Vacuum significant change platform."""
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.vacuum import (
|
||||
ATTR_BATTERY_ICON,
|
||||
ATTR_BATTERY_LEVEL,
|
||||
ATTR_FAN_SPEED,
|
||||
)
|
||||
from homeassistant.components.vacuum.significant_change import (
|
||||
async_check_significant_change,
|
||||
)
|
||||
|
||||
|
||||
async def test_significant_state_change() -> None:
|
||||
"""Detect Vacuum significant state changes."""
|
||||
attrs = {}
|
||||
assert not async_check_significant_change(None, "on", attrs, "on", attrs)
|
||||
assert async_check_significant_change(None, "on", attrs, "off", attrs)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("old_attrs", "new_attrs", "expected_result"),
|
||||
[
|
||||
({ATTR_FAN_SPEED: "old_value"}, {ATTR_FAN_SPEED: "old_value"}, False),
|
||||
({ATTR_FAN_SPEED: "old_value"}, {ATTR_FAN_SPEED: "new_value"}, True),
|
||||
# multiple attributes
|
||||
(
|
||||
{ATTR_FAN_SPEED: "old_value", ATTR_BATTERY_LEVEL: 10.0},
|
||||
{ATTR_FAN_SPEED: "new_value", ATTR_BATTERY_LEVEL: 10.0},
|
||||
True,
|
||||
),
|
||||
# float attributes
|
||||
({ATTR_BATTERY_LEVEL: 10.0}, {ATTR_BATTERY_LEVEL: 11.0}, True),
|
||||
({ATTR_BATTERY_LEVEL: 10.0}, {ATTR_BATTERY_LEVEL: 10.9}, False),
|
||||
({ATTR_BATTERY_LEVEL: "invalid"}, {ATTR_BATTERY_LEVEL: 10.0}, True),
|
||||
({ATTR_BATTERY_LEVEL: 10.0}, {ATTR_BATTERY_LEVEL: "invalid"}, False),
|
||||
# insignificant attributes
|
||||
({ATTR_BATTERY_ICON: "old_value"}, {ATTR_BATTERY_ICON: "new_value"}, False),
|
||||
({ATTR_BATTERY_ICON: "old_value"}, {ATTR_BATTERY_ICON: "old_value"}, False),
|
||||
({"unknown_attr": "old_value"}, {"unknown_attr": "old_value"}, False),
|
||||
({"unknown_attr": "old_value"}, {"unknown_attr": "new_value"}, False),
|
||||
],
|
||||
)
|
||||
async def test_significant_atributes_change(
|
||||
old_attrs: dict, new_attrs: dict, expected_result: bool
|
||||
) -> None:
|
||||
"""Detect Vacuum significant attribute changes."""
|
||||
assert (
|
||||
async_check_significant_change(None, "state", old_attrs, "state", new_attrs)
|
||||
== expected_result
|
||||
)
|
||||
Reference in New Issue
Block a user