Compare commits

...
Sign in to create a new pull request.

24 commits

Author SHA1 Message Date
G Johansson
7e487f9563 Mods 2024-11-13 17:20:15 +00:00
G Johansson
10a6f22add Fix deprecation version 2024-11-12 16:20:18 +00:00
G Johansson
c1581bc18b Fix Matter 2024-11-11 21:25:06 +00:00
G Johansson
bb4f13fdb5 Fix vacuum 2024-11-11 21:19:41 +00:00
G Johansson
3ec0704405 LG 2024-11-11 21:11:56 +00:00
G Johansson
e53d165db5 Mod roomba 2024-11-11 20:46:31 +00:00
G Johansson
f832cacb9a Update demo 2024-11-11 20:43:15 +00:00
G Johansson
6b50da1db2 Mods 2024-11-11 20:40:02 +00:00
G Johansson
02f496f465 Mods 2024-11-11 20:39:15 +00:00
G Johansson
e4835a31c7 Mods 2024-11-11 20:38:57 +00:00
G Johansson
9a9673aaa4 Fix VacuumEntity 2024-11-11 20:26:39 +00:00
G Johansson
4caaaf86bc Fixes 2024-11-11 20:13:56 +00:00
G Johansson
3cfef0cc86 Tests 2024-11-11 20:13:56 +00:00
G Johansson
aef136449f Fixes 2024-11-11 20:13:56 +00:00
G Johansson
2bc1b6bddd Litterrobot tests 2024-11-11 20:13:56 +00:00
G Johansson
ae64169b72 Fix last test 2024-11-11 20:13:56 +00:00
G Johansson
82aa779766 Add vacuum tests 2024-11-11 20:13:55 +00:00
G Johansson
1026758abd Fix state 2024-11-11 20:13:55 +00:00
G Johansson
d638a3b6e3 Tests 2024-11-11 20:13:55 +00:00
G Johansson
ea2055c403 Fix integrations 2024-11-11 20:13:55 +00:00
G Johansson
8ca4097dda Mods 2024-11-11 20:13:55 +00:00
G Johansson
2c3c9f057f Mod init 2024-11-11 20:13:55 +00:00
G Johansson
76574b5a12 Mod 2024-11-11 20:13:55 +00:00
G Johansson
9fd9d1f106 Implement new state property for vacuum which is using an enum 2024-11-11 20:13:55 +00:00
39 changed files with 843 additions and 505 deletions

View file

@ -436,7 +436,7 @@ class AlexaPowerController(AlexaCapability):
elif self.entity.domain == remote.DOMAIN:
is_on = self.entity.state not in (STATE_OFF, STATE_UNKNOWN)
elif self.entity.domain == vacuum.DOMAIN:
is_on = self.entity.state == vacuum.STATE_CLEANING
is_on = self.entity.state == vacuum.VacuumActivity.CLEANING
elif self.entity.domain == timer.DOMAIN:
is_on = self.entity.state != STATE_IDLE
elif self.entity.domain == water_heater.DOMAIN:

View file

@ -7,12 +7,8 @@ from typing import Any
from homeassistant.components.vacuum import (
ATTR_CLEANED_AREA,
STATE_CLEANING,
STATE_DOCKED,
STATE_IDLE,
STATE_PAUSED,
STATE_RETURNING,
StateVacuumEntity,
VacuumActivity,
VacuumEntityFeature,
)
from homeassistant.config_entries import ConfigEntry
@ -91,16 +87,11 @@ class StateDemoVacuum(StateVacuumEntity):
"""Initialize the vacuum."""
self._attr_name = name
self._attr_supported_features = supported_features
self._state = STATE_DOCKED
self._attr_activity = VacuumActivity.DOCKED
self._fan_speed = FAN_SPEEDS[1]
self._cleaned_area: float = 0
self._battery_level = 100
@property
def state(self) -> str:
"""Return the current state of the vacuum."""
return self._state
@property
def battery_level(self) -> int:
"""Return the current battery level of the vacuum."""
@ -123,33 +114,33 @@ class StateDemoVacuum(StateVacuumEntity):
def start(self) -> None:
"""Start or resume the cleaning task."""
if self._state != STATE_CLEANING:
self._state = STATE_CLEANING
if self._attr_activity != VacuumActivity.CLEANING:
self._attr_activity = VacuumActivity.CLEANING
self._cleaned_area += 1.32
self._battery_level -= 1
self.schedule_update_ha_state()
def pause(self) -> None:
"""Pause the cleaning task."""
if self._state == STATE_CLEANING:
self._state = STATE_PAUSED
if self._attr_activity == VacuumActivity.CLEANING:
self._attr_activity = VacuumActivity.PAUSED
self.schedule_update_ha_state()
def stop(self, **kwargs: Any) -> None:
"""Stop the cleaning task, do not return to dock."""
self._state = STATE_IDLE
self._attr_activity = VacuumActivity.IDLE
self.schedule_update_ha_state()
def return_to_base(self, **kwargs: Any) -> None:
"""Return dock to charging base."""
self._state = STATE_RETURNING
self._attr_activity = VacuumActivity.RETURNING
self.schedule_update_ha_state()
event.call_later(self.hass, 30, self.__set_state_to_dock)
def clean_spot(self, **kwargs: Any) -> None:
"""Perform a spot clean-up."""
self._state = STATE_CLEANING
self._attr_activity = VacuumActivity.CLEANING
self._cleaned_area += 1.32
self._battery_level -= 1
self.schedule_update_ha_state()
@ -167,12 +158,12 @@ class StateDemoVacuum(StateVacuumEntity):
"persistent_notification",
service_data={"message": "I'm here!", "title": "Locate request"},
)
self._state = STATE_IDLE
self._attr_activity = VacuumActivity.IDLE
self.async_write_ha_state()
async def async_clean_spot(self, **kwargs: Any) -> None:
"""Locate the vacuum's position."""
self._state = STATE_CLEANING
self._attr_activity = VacuumActivity.CLEANING
self.async_write_ha_state()
async def async_send_command(
@ -182,9 +173,9 @@ class StateDemoVacuum(StateVacuumEntity):
**kwargs: Any,
) -> None:
"""Send a command to the vacuum."""
self._state = STATE_IDLE
self._attr_activity = VacuumActivity.IDLE
self.async_write_ha_state()
def __set_state_to_dock(self, _: datetime) -> None:
self._state = STATE_DOCKED
self._attr_activity = VacuumActivity.DOCKED
self.schedule_update_ha_state()

View file

@ -13,14 +13,9 @@ from deebot_client.models import CleanAction, CleanMode, Room, State
import sucks
from homeassistant.components.vacuum import (
STATE_CLEANING,
STATE_DOCKED,
STATE_ERROR,
STATE_IDLE,
STATE_PAUSED,
STATE_RETURNING,
StateVacuumEntity,
StateVacuumEntityDescription,
VacuumActivity,
VacuumEntityFeature,
)
from homeassistant.core import HomeAssistant, SupportsResponse
@ -123,22 +118,22 @@ class EcovacsLegacyVacuum(EcovacsLegacyEntity, StateVacuumEntity):
self.schedule_update_ha_state()
@property
def state(self) -> str | None:
def activity(self) -> VacuumActivity | None:
"""Return the state of the vacuum cleaner."""
if self.error is not None:
return STATE_ERROR
return VacuumActivity.ERROR
if self.device.is_cleaning:
return STATE_CLEANING
return VacuumActivity.CLEANING
if self.device.is_charging:
return STATE_DOCKED
return VacuumActivity.DOCKED
if self.device.vacuum_status == sucks.CLEAN_MODE_STOP:
return STATE_IDLE
return VacuumActivity.IDLE
if self.device.vacuum_status == sucks.CHARGE_MODE_RETURNING:
return STATE_RETURNING
return VacuumActivity.RETURNING
return None
@ -202,7 +197,7 @@ class EcovacsLegacyVacuum(EcovacsLegacyEntity, StateVacuumEntity):
def set_fan_speed(self, fan_speed: str, **kwargs: Any) -> None:
"""Set fan speed."""
if self.state == STATE_CLEANING:
if self.state == VacuumActivity.CLEANING:
self.device.run(sucks.Clean(mode=self.device.clean_status, speed=fan_speed))
def send_command(
@ -225,12 +220,12 @@ class EcovacsLegacyVacuum(EcovacsLegacyEntity, StateVacuumEntity):
_STATE_TO_VACUUM_STATE = {
State.IDLE: STATE_IDLE,
State.CLEANING: STATE_CLEANING,
State.RETURNING: STATE_RETURNING,
State.DOCKED: STATE_DOCKED,
State.ERROR: STATE_ERROR,
State.PAUSED: STATE_PAUSED,
State.IDLE: VacuumActivity.IDLE,
State.CLEANING: VacuumActivity.CLEANING,
State.RETURNING: VacuumActivity.RETURNING,
State.DOCKED: VacuumActivity.DOCKED,
State.ERROR: VacuumActivity.ERROR,
State.PAUSED: VacuumActivity.PAUSED,
}
_ATTR_ROOMS = "rooms"
@ -284,7 +279,7 @@ class EcovacsVacuum(
self.async_write_ha_state()
async def on_status(event: StateEvent) -> None:
self._attr_state = _STATE_TO_VACUUM_STATE[event.state]
self._attr_activity = _STATE_TO_VACUUM_STATE[event.state]
self.async_write_ha_state()
self._subscribe(self._capability.battery.event, on_battery)

View file

@ -729,7 +729,7 @@ class DockTrait(_Trait):
def query_attributes(self) -> dict[str, Any]:
"""Return dock query attributes."""
return {"isDocked": self.state.state == vacuum.STATE_DOCKED}
return {"isDocked": self.state.state == vacuum.VacuumActivity.DOCKED}
async def execute(self, command, data, params, challenge):
"""Execute a dock command."""
@ -825,8 +825,8 @@ class EnergyStorageTrait(_Trait):
"capacityUntilFull": [
{"rawValue": 100 - battery_level, "unit": "PERCENTAGE"}
],
"isCharging": self.state.state == vacuum.STATE_DOCKED,
"isPluggedIn": self.state.state == vacuum.STATE_DOCKED,
"isCharging": self.state.state == vacuum.VacuumActivity.DOCKED,
"isPluggedIn": self.state.state == vacuum.VacuumActivity.DOCKED,
}
async def execute(self, command, data, params, challenge):
@ -882,8 +882,8 @@ class StartStopTrait(_Trait):
if domain == vacuum.DOMAIN:
return {
"isRunning": state == vacuum.STATE_CLEANING,
"isPaused": state == vacuum.STATE_PAUSED,
"isRunning": state == vacuum.VacuumActivity.CLEANING,
"isPaused": state == vacuum.VacuumActivity.PAUSED,
}
if domain in COVER_VALVE_DOMAINS:

View file

@ -11,7 +11,7 @@ from typing import Protocol
from homeassistant.components.alarm_control_panel import AlarmControlPanelState
from homeassistant.components.climate import HVACMode
from homeassistant.components.lock import LockState
from homeassistant.components.vacuum import STATE_CLEANING, STATE_ERROR, STATE_RETURNING
from homeassistant.components.vacuum import VacuumActivity
from homeassistant.components.water_heater import (
STATE_ECO,
STATE_ELECTRIC,
@ -105,9 +105,9 @@ ON_OFF_STATES: dict[Platform | str, tuple[set[str], str, str]] = {
Platform.VACUUM: (
{
STATE_ON,
STATE_CLEANING,
STATE_RETURNING,
STATE_ERROR,
VacuumActivity.CLEANING,
VacuumActivity.RETURNING,
VacuumActivity.ERROR,
},
STATE_ON,
STATE_OFF,

View file

@ -21,7 +21,7 @@ from homeassistant.components.vacuum import (
DOMAIN as VACUUM_DOMAIN,
SERVICE_RETURN_TO_BASE,
SERVICE_START,
STATE_CLEANING,
VacuumActivity,
VacuumEntityFeature,
)
from homeassistant.const import (
@ -213,7 +213,7 @@ class Vacuum(Switch):
@callback
def async_update_state(self, new_state: State) -> None:
"""Update switch state after state changed."""
current_state = new_state.state in (STATE_CLEANING, STATE_ON)
current_state = new_state.state in (VacuumActivity.CLEANING, STATE_ON)
_LOGGER.debug("%s: Set current state to %s", self.entity_id, current_state)
self.char_on.set_value(current_state)

View file

@ -9,15 +9,11 @@ from thinqconnect import DeviceType
from thinqconnect.integration import ExtendedProperty
from homeassistant.components.vacuum import (
STATE_CLEANING,
STATE_DOCKED,
STATE_ERROR,
STATE_RETURNING,
StateVacuumEntity,
StateVacuumEntityDescription,
VacuumActivity,
VacuumEntityFeature,
)
from homeassistant.const import STATE_IDLE, STATE_PAUSED
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
@ -46,21 +42,21 @@ class State(StrEnum):
ROBOT_STATUS_TO_HA = {
"charging": STATE_DOCKED,
"diagnosis": STATE_IDLE,
"homing": STATE_RETURNING,
"initializing": STATE_IDLE,
"macrosector": STATE_IDLE,
"monitoring_detecting": STATE_IDLE,
"monitoring_moving": STATE_IDLE,
"monitoring_positioning": STATE_IDLE,
"pause": STATE_PAUSED,
"reservation": STATE_IDLE,
"setdate": STATE_IDLE,
"sleep": STATE_IDLE,
"standby": STATE_IDLE,
"working": STATE_CLEANING,
"error": STATE_ERROR,
"charging": VacuumActivity.DOCKED,
"diagnosis": VacuumActivity.IDLE,
"homing": VacuumActivity.RETURNING,
"initializing": VacuumActivity.IDLE,
"macrosector": VacuumActivity.IDLE,
"monitoring_detecting": VacuumActivity.IDLE,
"monitoring_moving": VacuumActivity.IDLE,
"monitoring_positioning": VacuumActivity.IDLE,
"pause": VacuumActivity.PAUSED,
"reservation": VacuumActivity.IDLE,
"setdate": VacuumActivity.IDLE,
"sleep": VacuumActivity.IDLE,
"standby": VacuumActivity.IDLE,
"working": VacuumActivity.CLEANING,
"error": VacuumActivity.ERROR,
}
ROBOT_BATT_TO_HA = {
"moveless": 5,
@ -114,7 +110,7 @@ class ThinQStateVacuumEntity(ThinQEntity, StateVacuumEntity):
super()._update_status()
# Update state.
self._attr_state = ROBOT_STATUS_TO_HA[self.data.current_state]
self._attr_activity = ROBOT_STATUS_TO_HA[self.data.current_state]
# Update battery.
if (level := self.data.battery) is not None:
@ -135,7 +131,7 @@ class ThinQStateVacuumEntity(ThinQEntity, StateVacuumEntity):
"""Start the device."""
if self.data.current_state == State.SLEEP:
value = State.WAKE_UP
elif self._attr_state == STATE_PAUSED:
elif self._attr_activity == VacuumActivity.PAUSED:
value = State.RESUME
else:
value = State.START

View file

@ -10,12 +10,9 @@ from pylitterbot.enums import LitterBoxStatus
import voluptuous as vol
from homeassistant.components.vacuum import (
STATE_CLEANING,
STATE_DOCKED,
STATE_ERROR,
STATE_PAUSED,
StateVacuumEntity,
StateVacuumEntityDescription,
VacuumActivity,
VacuumEntityFeature,
)
from homeassistant.core import HomeAssistant
@ -29,16 +26,16 @@ from .entity import LitterRobotEntity
SERVICE_SET_SLEEP_MODE = "set_sleep_mode"
LITTER_BOX_STATUS_STATE_MAP = {
LitterBoxStatus.CLEAN_CYCLE: STATE_CLEANING,
LitterBoxStatus.EMPTY_CYCLE: STATE_CLEANING,
LitterBoxStatus.CLEAN_CYCLE_COMPLETE: STATE_DOCKED,
LitterBoxStatus.CAT_DETECTED: STATE_DOCKED,
LitterBoxStatus.CAT_SENSOR_TIMING: STATE_DOCKED,
LitterBoxStatus.DRAWER_FULL_1: STATE_DOCKED,
LitterBoxStatus.DRAWER_FULL_2: STATE_DOCKED,
LitterBoxStatus.READY: STATE_DOCKED,
LitterBoxStatus.CAT_SENSOR_INTERRUPTED: STATE_PAUSED,
LitterBoxStatus.OFF: STATE_DOCKED,
LitterBoxStatus.CLEAN_CYCLE: VacuumActivity.CLEANING,
LitterBoxStatus.EMPTY_CYCLE: VacuumActivity.CLEANING,
LitterBoxStatus.CLEAN_CYCLE_COMPLETE: VacuumActivity.DOCKED,
LitterBoxStatus.CAT_DETECTED: VacuumActivity.DOCKED,
LitterBoxStatus.CAT_SENSOR_TIMING: VacuumActivity.DOCKED,
LitterBoxStatus.DRAWER_FULL_1: VacuumActivity.DOCKED,
LitterBoxStatus.DRAWER_FULL_2: VacuumActivity.DOCKED,
LitterBoxStatus.READY: VacuumActivity.DOCKED,
LitterBoxStatus.CAT_SENSOR_INTERRUPTED: VacuumActivity.PAUSED,
LitterBoxStatus.OFF: VacuumActivity.DOCKED,
}
LITTER_BOX_ENTITY = StateVacuumEntityDescription(
@ -78,9 +75,9 @@ class LitterRobotCleaner(LitterRobotEntity[LitterRobot], StateVacuumEntity):
)
@property
def state(self) -> str:
def activity(self) -> VacuumActivity:
"""Return the state of the cleaner."""
return LITTER_BOX_STATUS_STATE_MAP.get(self.robot.status, STATE_ERROR)
return LITTER_BOX_STATUS_STATE_MAP.get(self.robot.status, VacuumActivity.ERROR)
@property
def status(self) -> str:

View file

@ -9,16 +9,13 @@ from chip.clusters import Objects as clusters
from matter_server.client.models import device_types
from homeassistant.components.vacuum import (
STATE_CLEANING,
STATE_DOCKED,
STATE_ERROR,
STATE_RETURNING,
StateVacuumEntity,
StateVacuumEntityDescription,
VacuumActivity,
VacuumEntityFeature,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import STATE_IDLE, Platform
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
@ -127,25 +124,25 @@ class MatterVacuum(MatterEntity, StateVacuumEntity):
operational_state: int = self.get_matter_attribute_value(
clusters.RvcOperationalState.Attributes.OperationalState
)
state: str | None = None
state: VacuumActivity | None = None
if TYPE_CHECKING:
assert self._supported_run_modes is not None
if operational_state in (OperationalState.CHARGING, OperationalState.DOCKED):
state = STATE_DOCKED
state = VacuumActivity.DOCKED
elif operational_state == OperationalState.SEEKING_CHARGER:
state = STATE_RETURNING
state = VacuumActivity.RETURNING
elif operational_state in (
OperationalState.UNABLE_TO_COMPLETE_OPERATION,
OperationalState.UNABLE_TO_START_OR_RESUME,
):
state = STATE_ERROR
state = VacuumActivity.ERROR
elif (run_mode := self._supported_run_modes.get(run_mode_raw)) is not None:
tags = {x.value for x in run_mode.modeTags}
if ModeTag.CLEANING in tags:
state = STATE_CLEANING
state = VacuumActivity.CLEANING
elif ModeTag.IDLE in tags:
state = STATE_IDLE
self._attr_state = state
state = VacuumActivity.IDLE
self._attr_activity = state
@callback
def _calculate_features(self) -> None:

View file

@ -10,20 +10,12 @@ import voluptuous as vol
from homeassistant.components import vacuum
from homeassistant.components.vacuum import (
ENTITY_ID_FORMAT,
STATE_CLEANING,
STATE_DOCKED,
STATE_ERROR,
STATE_RETURNING,
StateVacuumEntity,
VacuumActivity,
VacuumEntityFeature,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
ATTR_SUPPORTED_FEATURES,
CONF_NAME,
STATE_IDLE,
STATE_PAUSED,
)
from homeassistant.const import ATTR_SUPPORTED_FEATURES, CONF_NAME
from homeassistant.core import HomeAssistant, callback
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity_platform import AddEntitiesCallback
@ -43,13 +35,20 @@ BATTERY = "battery_level"
FAN_SPEED = "fan_speed"
STATE = "state"
POSSIBLE_STATES: dict[str, str] = {
STATE_IDLE: STATE_IDLE,
STATE_DOCKED: STATE_DOCKED,
STATE_ERROR: STATE_ERROR,
STATE_PAUSED: STATE_PAUSED,
STATE_RETURNING: STATE_RETURNING,
STATE_CLEANING: STATE_CLEANING,
STATE_IDLE = "idle"
STATE_DOCKED = "docked"
STATE_ERROR = "error"
STATE_PAUSED = "paused"
STATE_RETURNING = "returning"
STATE_CLEANING = "cleaning"
POSSIBLE_STATES: dict[str, VacuumActivity] = {
STATE_IDLE: VacuumActivity.IDLE,
STATE_DOCKED: VacuumActivity.DOCKED,
STATE_ERROR: VacuumActivity.ERROR,
STATE_PAUSED: VacuumActivity.PAUSED,
STATE_RETURNING: VacuumActivity.RETURNING,
STATE_CLEANING: VacuumActivity.CLEANING,
}
CONF_SUPPORTED_FEATURES = ATTR_SUPPORTED_FEATURES
@ -263,7 +262,7 @@ class MqttStateVacuum(MqttEntity, StateVacuumEntity):
if STATE in payload and (
(state := payload[STATE]) in POSSIBLE_STATES or state is None
):
self._attr_state = (
self._attr_activity = (
POSSIBLE_STATES[cast(str, state)] if payload[STATE] else None
)
del payload[STATE]
@ -275,7 +274,7 @@ class MqttStateVacuum(MqttEntity, StateVacuumEntity):
self.add_subscription(
CONF_STATE_TOPIC,
self._state_message_received,
{"_attr_battery_level", "_attr_fan_speed", "_attr_state"},
{"_attr_battery_level", "_attr_fan_speed", "_attr_activity"},
)
async def _subscribe_topics(self) -> None:

View file

@ -12,15 +12,12 @@ import voluptuous as vol
from homeassistant.components.vacuum import (
ATTR_STATUS,
STATE_CLEANING,
STATE_DOCKED,
STATE_ERROR,
STATE_RETURNING,
StateVacuumEntity,
VacuumActivity,
VacuumEntityFeature,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ATTR_MODE, STATE_IDLE, STATE_PAUSED
from homeassistant.const import ATTR_MODE
from homeassistant.core import HomeAssistant
from homeassistant.helpers import config_validation as cv, entity_platform
from homeassistant.helpers.device_registry import DeviceInfo
@ -169,23 +166,23 @@ class NeatoConnectedVacuum(NeatoEntity, StateVacuumEntity):
robot_alert = None
if self._state["state"] == 1:
if self._state["details"]["isCharging"]:
self._attr_state = STATE_DOCKED
self._attr_activity = VacuumActivity.DOCKED
self._status_state = "Charging"
elif (
self._state["details"]["isDocked"]
and not self._state["details"]["isCharging"]
):
self._attr_state = STATE_DOCKED
self._attr_activity = VacuumActivity.DOCKED
self._status_state = "Docked"
else:
self._attr_state = STATE_IDLE
self._attr_activity = VacuumActivity.IDLE
self._status_state = "Stopped"
if robot_alert is not None:
self._status_state = robot_alert
elif self._state["state"] == 2:
if robot_alert is None:
self._attr_state = STATE_CLEANING
self._attr_activity = VacuumActivity.CLEANING
self._status_state = (
f"{MODE.get(self._state['cleaning']['mode'])} "
f"{ACTION.get(self._state['action'])}"
@ -200,10 +197,10 @@ class NeatoConnectedVacuum(NeatoEntity, StateVacuumEntity):
else:
self._status_state = robot_alert
elif self._state["state"] == 3:
self._attr_state = STATE_PAUSED
self._attr_activity = VacuumActivity.PAUSED
self._status_state = "Paused"
elif self._state["state"] == 4:
self._attr_state = STATE_ERROR
self._attr_activity = VacuumActivity.ERROR
self._status_state = ERRORS.get(self._state["error"])
self._attr_battery_level = self._state["details"]["charge"]
@ -326,9 +323,9 @@ class NeatoConnectedVacuum(NeatoEntity, StateVacuumEntity):
def return_to_base(self, **kwargs: Any) -> None:
"""Set the vacuum cleaner to return to the dock."""
try:
if self._attr_state == STATE_CLEANING:
if self._attr_activity == VacuumActivity.CLEANING:
self.robot.pause_cleaning()
self._attr_state = STATE_RETURNING
self._attr_activity = VacuumActivity.RETURNING
self.robot.send_to_base()
except NeatoRobotException as ex:
_LOGGER.error(
@ -380,7 +377,7 @@ class NeatoConnectedVacuum(NeatoEntity, StateVacuumEntity):
"Start cleaning zone '%s' with robot %s", zone, self.entity_id
)
self._attr_state = STATE_CLEANING
self._attr_activity = VacuumActivity.CLEANING
try:
self.robot.start_cleaning(mode, navigation, category, boundary_id)
except NeatoRobotException as ex:

View file

@ -8,13 +8,8 @@ from roborock.roborock_message import RoborockDataProtocol
from roborock.roborock_typing import RoborockCommand
from homeassistant.components.vacuum import (
STATE_CLEANING,
STATE_DOCKED,
STATE_ERROR,
STATE_IDLE,
STATE_PAUSED,
STATE_RETURNING,
StateVacuumEntity,
VacuumActivity,
VacuumEntityFeature,
)
from homeassistant.core import HomeAssistant, ServiceResponse, SupportsResponse
@ -27,29 +22,29 @@ from .coordinator import RoborockDataUpdateCoordinator
from .entity import RoborockCoordinatedEntityV1
STATE_CODE_TO_STATE = {
RoborockStateCode.starting: STATE_IDLE, # "Starting"
RoborockStateCode.charger_disconnected: STATE_IDLE, # "Charger disconnected"
RoborockStateCode.idle: STATE_IDLE, # "Idle"
RoborockStateCode.remote_control_active: STATE_CLEANING, # "Remote control active"
RoborockStateCode.cleaning: STATE_CLEANING, # "Cleaning"
RoborockStateCode.returning_home: STATE_RETURNING, # "Returning home"
RoborockStateCode.manual_mode: STATE_CLEANING, # "Manual mode"
RoborockStateCode.charging: STATE_DOCKED, # "Charging"
RoborockStateCode.charging_problem: STATE_ERROR, # "Charging problem"
RoborockStateCode.paused: STATE_PAUSED, # "Paused"
RoborockStateCode.spot_cleaning: STATE_CLEANING, # "Spot cleaning"
RoborockStateCode.error: STATE_ERROR, # "Error"
RoborockStateCode.shutting_down: STATE_IDLE, # "Shutting down"
RoborockStateCode.updating: STATE_DOCKED, # "Updating"
RoborockStateCode.docking: STATE_RETURNING, # "Docking"
RoborockStateCode.going_to_target: STATE_CLEANING, # "Going to target"
RoborockStateCode.zoned_cleaning: STATE_CLEANING, # "Zoned cleaning"
RoborockStateCode.segment_cleaning: STATE_CLEANING, # "Segment cleaning"
RoborockStateCode.emptying_the_bin: STATE_DOCKED, # "Emptying the bin" on s7+
RoborockStateCode.washing_the_mop: STATE_DOCKED, # "Washing the mop" on s7maxV
RoborockStateCode.going_to_wash_the_mop: STATE_RETURNING, # "Going to wash the mop" on s7maxV
RoborockStateCode.charging_complete: STATE_DOCKED, # "Charging complete"
RoborockStateCode.device_offline: STATE_ERROR, # "Device offline"
RoborockStateCode.starting: VacuumActivity.IDLE, # "Starting"
RoborockStateCode.charger_disconnected: VacuumActivity.IDLE, # "Charger disconnected"
RoborockStateCode.idle: VacuumActivity.IDLE, # "Idle"
RoborockStateCode.remote_control_active: VacuumActivity.CLEANING, # "Remote control active"
RoborockStateCode.cleaning: VacuumActivity.CLEANING, # "Cleaning"
RoborockStateCode.returning_home: VacuumActivity.RETURNING, # "Returning home"
RoborockStateCode.manual_mode: VacuumActivity.CLEANING, # "Manual mode"
RoborockStateCode.charging: VacuumActivity.DOCKED, # "Charging"
RoborockStateCode.charging_problem: VacuumActivity.ERROR, # "Charging problem"
RoborockStateCode.paused: VacuumActivity.PAUSED, # "Paused"
RoborockStateCode.spot_cleaning: VacuumActivity.CLEANING, # "Spot cleaning"
RoborockStateCode.error: VacuumActivity.ERROR, # "Error"
RoborockStateCode.shutting_down: VacuumActivity.IDLE, # "Shutting down"
RoborockStateCode.updating: VacuumActivity.DOCKED, # "Updating"
RoborockStateCode.docking: VacuumActivity.RETURNING, # "Docking"
RoborockStateCode.going_to_target: VacuumActivity.CLEANING, # "Going to target"
RoborockStateCode.zoned_cleaning: VacuumActivity.CLEANING, # "Zoned cleaning"
RoborockStateCode.segment_cleaning: VacuumActivity.CLEANING, # "Segment cleaning"
RoborockStateCode.emptying_the_bin: VacuumActivity.DOCKED, # "Emptying the bin" on s7+
RoborockStateCode.washing_the_mop: VacuumActivity.DOCKED, # "Washing the mop" on s7maxV
RoborockStateCode.going_to_wash_the_mop: VacuumActivity.RETURNING, # "Going to wash the mop" on s7maxV
RoborockStateCode.charging_complete: VacuumActivity.DOCKED, # "Charging complete"
RoborockStateCode.device_offline: VacuumActivity.ERROR, # "Device offline"
}
@ -112,7 +107,7 @@ class RoborockVacuum(RoborockCoordinatedEntityV1, StateVacuumEntity):
self._attr_fan_speed_list = self._device_status.fan_power_options
@property
def state(self) -> str | None:
def activity(self) -> VacuumActivity | None:
"""Return the status of the vacuum cleaner."""
assert self._device_status.state is not None
return STATE_CODE_TO_STATE.get(self._device_status.state)

View file

@ -6,7 +6,11 @@ https://home-assistant.io/components/vacuum.romy/.
from typing import Any
from homeassistant.components.vacuum import StateVacuumEntity, VacuumEntityFeature
from homeassistant.components.vacuum import (
StateVacuumEntity,
VacuumActivity,
VacuumEntityFeature,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
@ -75,7 +79,11 @@ class RomyVacuumEntity(RomyEntity, StateVacuumEntity):
"""Handle updated data from the coordinator."""
self._attr_fan_speed = FAN_SPEEDS[self.romy.fan_speed]
self._attr_battery_level = self.romy.battery_level
self._attr_state = self.romy.status
try:
assert self.romy.status is not None
self._attr_activity = VacuumActivity(self.romy.status)
except (AssertionError, ValueError):
self._attr_activity = None
self.async_write_ha_state()

View file

@ -7,14 +7,11 @@ import logging
from homeassistant.components.vacuum import (
ATTR_STATUS,
STATE_CLEANING,
STATE_DOCKED,
STATE_ERROR,
STATE_RETURNING,
StateVacuumEntity,
VacuumActivity,
VacuumEntityFeature,
)
from homeassistant.const import ATTR_CONNECTIONS, STATE_IDLE, STATE_PAUSED
from homeassistant.const import ATTR_CONNECTIONS
import homeassistant.helpers.device_registry as dr
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity import Entity
@ -46,16 +43,16 @@ SUPPORT_IROBOT = (
)
STATE_MAP = {
"": STATE_IDLE,
"charge": STATE_DOCKED,
"evac": STATE_RETURNING, # Emptying at cleanbase
"hmMidMsn": STATE_CLEANING, # Recharging at the middle of a cycle
"hmPostMsn": STATE_RETURNING, # Cycle finished
"hmUsrDock": STATE_RETURNING,
"pause": STATE_PAUSED,
"run": STATE_CLEANING,
"stop": STATE_IDLE,
"stuck": STATE_ERROR,
"": VacuumActivity.IDLE,
"charge": VacuumActivity.DOCKED,
"evac": VacuumActivity.RETURNING, # Emptying at cleanbase
"hmMidMsn": VacuumActivity.CLEANING, # Recharging at the middle of a cycle
"hmPostMsn": VacuumActivity.RETURNING, # Cycle finished
"hmUsrDock": VacuumActivity.RETURNING,
"pause": VacuumActivity.PAUSED,
"run": VacuumActivity.CLEANING,
"stop": VacuumActivity.IDLE,
"stuck": VacuumActivity.ERROR,
}
@ -128,7 +125,7 @@ class IRobotEntity(Entity):
return dt_util.utc_from_timestamp(ts)
@property
def _robot_state(self):
def _robot_state(self) -> VacuumActivity:
"""Return the state of the vacuum cleaner."""
clean_mission_status = self.vacuum_state.get("cleanMissionStatus", {})
cycle = clean_mission_status.get("cycle")
@ -136,9 +133,12 @@ class IRobotEntity(Entity):
try:
state = STATE_MAP[phase]
except KeyError:
return STATE_ERROR
if cycle != "none" and state in (STATE_IDLE, STATE_DOCKED):
state = STATE_PAUSED
return VacuumActivity.ERROR
if cycle != "none" and state in (
VacuumActivity.IDLE,
VacuumActivity.DOCKED,
):
state = VacuumActivity.PAUSED
return state
async def async_added_to_hass(self):
@ -169,7 +169,7 @@ class IRobotVacuum(IRobotEntity, StateVacuumEntity): # pylint: disable=hass-enf
self._cap_position = self.vacuum_state.get("cap", {}).get("pose") == 1
@property
def state(self):
def activity(self) -> VacuumActivity:
"""Return the state of the vacuum cleaner."""
return self._robot_state
@ -189,7 +189,7 @@ class IRobotVacuum(IRobotEntity, StateVacuumEntity): # pylint: disable=hass-enf
# Only add cleaning time and cleaned area attrs when the vacuum is
# currently on
if self.state == STATE_CLEANING:
if self.state == VacuumActivity.CLEANING:
# Get clean mission status
(
state_attrs[ATTR_CLEANING_TIME],
@ -243,7 +243,7 @@ class IRobotVacuum(IRobotEntity, StateVacuumEntity): # pylint: disable=hass-enf
async def async_start(self):
"""Start or resume the cleaning task."""
if self.state == STATE_PAUSED:
if self.vacuum_state == VacuumActivity.PAUSED:
await self.hass.async_add_executor_job(self.vacuum.send_command, "resume")
else:
await self.hass.async_add_executor_job(self.vacuum.send_command, "start")
@ -258,10 +258,10 @@ class IRobotVacuum(IRobotEntity, StateVacuumEntity): # pylint: disable=hass-enf
async def async_return_to_base(self, **kwargs):
"""Set the vacuum cleaner to return to the dock."""
if self.state == STATE_CLEANING:
if self.vacuum_state == VacuumActivity.CLEANING:
await self.async_pause()
for _ in range(10):
if self.state == STATE_PAUSED:
if self.state == VacuumActivity.PAUSED:
break
await asyncio.sleep(1)
await self.hass.async_add_executor_job(self.vacuum.send_command, "dock")

View file

@ -9,12 +9,8 @@ from sharkiq import OperatingModes, PowerModes, Properties, SharkIqVacuum
import voluptuous as vol
from homeassistant.components.vacuum import (
STATE_CLEANING,
STATE_DOCKED,
STATE_IDLE,
STATE_PAUSED,
STATE_RETURNING,
StateVacuumEntity,
VacuumActivity,
VacuumEntityFeature,
)
from homeassistant.config_entries import ConfigEntry
@ -30,10 +26,10 @@ from .const import DOMAIN, LOGGER, SERVICE_CLEAN_ROOM, SHARK
from .coordinator import SharkIqUpdateCoordinator
OPERATING_STATE_MAP = {
OperatingModes.PAUSE: STATE_PAUSED,
OperatingModes.START: STATE_CLEANING,
OperatingModes.STOP: STATE_IDLE,
OperatingModes.RETURN: STATE_RETURNING,
OperatingModes.PAUSE: VacuumActivity.PAUSED,
OperatingModes.START: VacuumActivity.CLEANING,
OperatingModes.STOP: VacuumActivity.IDLE,
OperatingModes.RETURN: VacuumActivity.RETURNING,
}
FAN_SPEEDS_MAP = {
@ -151,7 +147,7 @@ class SharkVacuumEntity(CoordinatorEntity[SharkIqUpdateCoordinator], StateVacuum
return self.sharkiq.error_text
@property
def operating_mode(self) -> str | None:
def operating_mode(self) -> VacuumActivity | None:
"""Operating mode."""
op_mode = self.sharkiq.get_property_value(Properties.OPERATING_MODE)
return OPERATING_STATE_MAP.get(op_mode)
@ -162,7 +158,7 @@ class SharkVacuumEntity(CoordinatorEntity[SharkIqUpdateCoordinator], StateVacuum
return self.sharkiq.get_property_value(Properties.RECHARGING_TO_RESUME)
@property
def state(self) -> str | None:
def activity(self) -> VacuumActivity | None:
"""Get the current vacuum state.
NB: Currently, we do not return an error state because they can be very, very stale.
@ -170,7 +166,7 @@ class SharkVacuumEntity(CoordinatorEntity[SharkIqUpdateCoordinator], StateVacuum
user a notification.
"""
if self.sharkiq.get_property_value(Properties.CHARGING_STATUS):
return STATE_DOCKED
return VacuumActivity.DOCKED
return self.operating_mode
@property

View file

@ -5,13 +5,8 @@ from typing import Any
from switchbot_api import Device, Remote, SwitchBotAPI, VacuumCommands
from homeassistant.components.vacuum import (
STATE_CLEANING,
STATE_DOCKED,
STATE_ERROR,
STATE_IDLE,
STATE_PAUSED,
STATE_RETURNING,
StateVacuumEntity,
VacuumActivity,
VacuumEntityFeature,
)
from homeassistant.config_entries import ConfigEntry
@ -43,17 +38,17 @@ async def async_setup_entry(
)
VACUUM_SWITCHBOT_STATE_TO_HA_STATE: dict[str, str] = {
"StandBy": STATE_IDLE,
"Clearing": STATE_CLEANING,
"Paused": STATE_PAUSED,
"GotoChargeBase": STATE_RETURNING,
"Charging": STATE_DOCKED,
"ChargeDone": STATE_DOCKED,
"Dormant": STATE_IDLE,
"InTrouble": STATE_ERROR,
"InRemoteControl": STATE_CLEANING,
"InDustCollecting": STATE_DOCKED,
VACUUM_SWITCHBOT_STATE_TO_HA_STATE: dict[str, VacuumActivity] = {
"StandBy": VacuumActivity.IDLE,
"Clearing": VacuumActivity.CLEANING,
"Paused": VacuumActivity.PAUSED,
"GotoChargeBase": VacuumActivity.RETURNING,
"Charging": VacuumActivity.DOCKED,
"ChargeDone": VacuumActivity.DOCKED,
"Dormant": VacuumActivity.IDLE,
"InTrouble": VacuumActivity.ERROR,
"InRemoteControl": VacuumActivity.CLEANING,
"InDustCollecting": VacuumActivity.DOCKED,
}
VACUUM_FAN_SPEED_TO_SWITCHBOT_FAN_SPEED: dict[str, str] = {
@ -114,7 +109,7 @@ class SwitchBotCloudVacuum(SwitchBotCloudEntity, StateVacuumEntity):
self._attr_available = self.coordinator.data.get("onlineStatus") == "online"
switchbot_state = str(self.coordinator.data.get("workingStatus"))
self._attr_state = VACUUM_SWITCHBOT_STATE_TO_HA_STATE.get(switchbot_state)
self._attr_activity = VACUUM_SWITCHBOT_STATE_TO_HA_STATE.get(switchbot_state)
self.async_write_ha_state()

View file

@ -17,13 +17,8 @@ from homeassistant.components.vacuum import (
SERVICE_SET_FAN_SPEED,
SERVICE_START,
SERVICE_STOP,
STATE_CLEANING,
STATE_DOCKED,
STATE_ERROR,
STATE_IDLE,
STATE_PAUSED,
STATE_RETURNING,
StateVacuumEntity,
VacuumActivity,
VacuumEntityFeature,
)
from homeassistant.const import (
@ -58,12 +53,12 @@ CONF_FAN_SPEED_TEMPLATE = "fan_speed_template"
ENTITY_ID_FORMAT = VACUUM_DOMAIN + ".{}"
_VALID_STATES = [
STATE_CLEANING,
STATE_DOCKED,
STATE_PAUSED,
STATE_IDLE,
STATE_RETURNING,
STATE_ERROR,
VacuumActivity.CLEANING,
VacuumActivity.DOCKED,
VacuumActivity.PAUSED,
VacuumActivity.IDLE,
VacuumActivity.RETURNING,
VacuumActivity.ERROR,
]
VACUUM_SCHEMA = vol.All(
@ -202,7 +197,7 @@ class TemplateVacuum(TemplateEntity, StateVacuumEntity):
self._attr_fan_speed_list = config[CONF_FAN_SPEED_LIST]
@property
def state(self) -> str | None:
def activity(self) -> VacuumActivity | None:
"""Return the status of the vacuum cleaner."""
return self._state

View file

@ -7,13 +7,10 @@ from typing import Any
from tuya_sharing import CustomerDevice, Manager
from homeassistant.components.vacuum import (
STATE_CLEANING,
STATE_DOCKED,
STATE_RETURNING,
StateVacuumEntity,
VacuumActivity,
VacuumEntityFeature,
)
from homeassistant.const import STATE_IDLE, STATE_PAUSED
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddEntitiesCallback
@ -24,29 +21,29 @@ from .entity import EnumTypeData, IntegerTypeData, TuyaEntity
TUYA_MODE_RETURN_HOME = "chargego"
TUYA_STATUS_TO_HA = {
"charge_done": STATE_DOCKED,
"chargecompleted": STATE_DOCKED,
"chargego": STATE_DOCKED,
"charging": STATE_DOCKED,
"cleaning": STATE_CLEANING,
"docking": STATE_RETURNING,
"goto_charge": STATE_RETURNING,
"goto_pos": STATE_CLEANING,
"mop_clean": STATE_CLEANING,
"part_clean": STATE_CLEANING,
"paused": STATE_PAUSED,
"pick_zone_clean": STATE_CLEANING,
"pos_arrived": STATE_CLEANING,
"pos_unarrive": STATE_CLEANING,
"random": STATE_CLEANING,
"sleep": STATE_IDLE,
"smart_clean": STATE_CLEANING,
"smart": STATE_CLEANING,
"spot_clean": STATE_CLEANING,
"standby": STATE_IDLE,
"wall_clean": STATE_CLEANING,
"wall_follow": STATE_CLEANING,
"zone_clean": STATE_CLEANING,
"charge_done": VacuumActivity.DOCKED,
"chargecompleted": VacuumActivity.DOCKED,
"chargego": VacuumActivity.DOCKED,
"charging": VacuumActivity.DOCKED,
"cleaning": VacuumActivity.CLEANING,
"docking": VacuumActivity.RETURNING,
"goto_charge": VacuumActivity.RETURNING,
"goto_pos": VacuumActivity.CLEANING,
"mop_clean": VacuumActivity.CLEANING,
"part_clean": VacuumActivity.CLEANING,
"paused": VacuumActivity.PAUSED,
"pick_zone_clean": VacuumActivity.CLEANING,
"pos_arrived": VacuumActivity.CLEANING,
"pos_unarrive": VacuumActivity.CLEANING,
"random": VacuumActivity.CLEANING,
"sleep": VacuumActivity.IDLE,
"smart_clean": VacuumActivity.CLEANING,
"smart": VacuumActivity.CLEANING,
"spot_clean": VacuumActivity.CLEANING,
"standby": VacuumActivity.IDLE,
"wall_clean": VacuumActivity.CLEANING,
"wall_follow": VacuumActivity.CLEANING,
"zone_clean": VacuumActivity.CLEANING,
}
@ -137,12 +134,12 @@ class TuyaVacuumEntity(TuyaEntity, StateVacuumEntity):
return self.device.status.get(DPCode.SUCTION)
@property
def state(self) -> str | None:
def activity(self) -> VacuumActivity | None:
"""Return Tuya vacuum device state."""
if self.device.status.get(DPCode.PAUSE) and not (
self.device.status.get(DPCode.STATUS)
):
return STATE_PAUSED
return VacuumActivity.PAUSED
if not (status := self.device.status.get(DPCode.STATUS)):
return None
return TUYA_STATUS_TO_HA.get(status)

View file

@ -2,11 +2,12 @@
from __future__ import annotations
import asyncio
from datetime import timedelta
from enum import IntFlag
from functools import partial
import logging
from typing import Any
from typing import TYPE_CHECKING, Any, final
from propcache import cached_property
import voluptuous as vol
@ -18,11 +19,9 @@ from homeassistant.const import ( # noqa: F401 # STATE_PAUSED/IDLE are API
SERVICE_TOGGLE,
SERVICE_TURN_OFF,
SERVICE_TURN_ON,
STATE_IDLE,
STATE_ON,
STATE_PAUSED,
)
from homeassistant.core import HomeAssistant
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.deprecation import (
DeprecatedConstantEnum,
@ -32,12 +31,22 @@ from homeassistant.helpers.deprecation 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 report
from homeassistant.helpers.icon import icon_for_battery_level
from homeassistant.helpers.typing import ConfigType
from homeassistant.loader import bind_hass
from homeassistant.util.hass_dict import HassKey
from .const import DOMAIN, STATE_CLEANING, STATE_DOCKED, STATE_ERROR, STATE_RETURNING
from .const import ( # noqa: F401
_DEPRECATED_STATE_CLEANING,
_DEPRECATED_STATE_DOCKED,
_DEPRECATED_STATE_ERROR,
_DEPRECATED_STATE_RETURNING,
DOMAIN,
STATES,
VacuumActivity,
)
_LOGGER = logging.getLogger(__name__)
@ -64,11 +73,13 @@ SERVICE_START = "start"
SERVICE_PAUSE = "pause"
SERVICE_STOP = "stop"
STATES = [STATE_CLEANING, STATE_DOCKED, STATE_RETURNING, STATE_ERROR]
DEFAULT_NAME = "Vacuum cleaner robot"
# These STATE_* constants are deprecated as of Home Assistant 2024.11.
# Please use the VacuumActivity enum instead.
_DEPRECATED_STATE_IDLE = DeprecatedConstantEnum(VacuumActivity.IDLE, "2025.12")
_DEPRECATED_STATE_PAUSED = DeprecatedConstantEnum(VacuumActivity.PAUSED, "2025.12")
class VacuumEntityFeature(IntFlag):
"""Supported features of the vacuum entity."""
@ -216,7 +227,7 @@ STATE_VACUUM_CACHED_PROPERTIES_WITH_ATTR_ = {
"battery_icon",
"fan_speed",
"fan_speed_list",
"state",
"activity",
}
@ -233,9 +244,55 @@ class StateVacuumEntity(
_attr_battery_level: int | None = None
_attr_fan_speed: str | None = None
_attr_fan_speed_list: list[str]
_attr_state: str | None = None
_attr_activity: VacuumActivity | None = None
_attr_supported_features: VacuumEntityFeature = VacuumEntityFeature(0)
__vacuum_legacy_state: bool = False
def __init_subclass__(cls, **kwargs: Any) -> None:
"""Post initialisation processing."""
super().__init_subclass__(**kwargs)
if any(method in cls.__dict__ for method in ("_attr_state", "state")):
# Integrations should use the 'activity' property instead of
# setting the state directly.
cls.__vacuum_legacy_state = True
def __setattr__(self, __name: str, __value: Any) -> None:
"""Set attribute.
Deprecation warning if setting '_attr_state' directly
unless already reported.
"""
if __name == "_attr_state":
self._report_deprecated_activity_handling()
return super().__setattr__(__name, __value)
@callback
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_state:
self._report_deprecated_activity_handling()
@callback
def _report_deprecated_activity_handling(self) -> None:
"""Report on deprecated handling of vacuum state.
Integrations should implement activity instead of using state directly.
"""
report(
"is setting state directly which will stop working in HA Core 2025.12."
f" Entity {self.entity_id} ({type(self)}) should implement the 'activity'"
" property and return its state using the VacuumActivity enum.",
error_if_core=True,
error_if_integration=False,
)
@cached_property
def battery_level(self) -> int | None:
"""Return the battery level of the vacuum cleaner."""
@ -244,7 +301,7 @@ class StateVacuumEntity(
@property
def battery_icon(self) -> str:
"""Return the battery icon for the vacuum cleaner."""
charging = bool(self.state == STATE_DOCKED)
charging = bool(self.activity == VacuumActivity.DOCKED)
return icon_for_battery_level(
battery_level=self.battery_level, charging=charging
@ -282,10 +339,28 @@ class StateVacuumEntity(
return data
@cached_property
@final
@property
def state(self) -> str | None:
"""Return the state of the vacuum cleaner."""
return self._attr_state
if (activity := self.activity) is not None:
return activity
if self._attr_state is not None:
# Backwards compatibility for integrations that set state directly
# Should be removed in 2025.12
if TYPE_CHECKING:
assert isinstance(self._attr_state, str)
return self._attr_state
return None
@cached_property
def activity(self) -> VacuumActivity | None:
"""Return the current vacuum activity.
Integrations should overwrite this or use the '_attr_activity'
attribute to set the vacuum activity using the 'VacuumActivity' enum.
"""
return self._attr_activity
@cached_property
def supported_features(self) -> VacuumEntityFeature:

View file

@ -1,10 +1,46 @@
"""Support for vacuum cleaner robots (botvacs)."""
from __future__ import annotations
from enum import StrEnum
from functools import partial
from homeassistant.helpers.deprecation import (
DeprecatedConstantEnum,
all_with_deprecated_constants,
check_if_deprecated_constant,
dir_with_deprecated_constants,
)
DOMAIN = "vacuum"
STATE_CLEANING = "cleaning"
STATE_DOCKED = "docked"
STATE_RETURNING = "returning"
STATE_ERROR = "error"
STATES = [STATE_CLEANING, STATE_DOCKED, STATE_RETURNING, STATE_ERROR]
class VacuumActivity(StrEnum):
"""Vacuum activity states."""
CLEANING = "cleaning"
DOCKED = "docked"
IDLE = "idle"
PAUSED = "paused"
RETURNING = "returning"
ERROR = "error"
# These STATE_* constants are deprecated as of Home Assistant 2024.11.
# Please use the VacuumActivity enum instead.
_DEPRECATED_STATE_CLEANING = DeprecatedConstantEnum(VacuumActivity.CLEANING, "2025.12")
_DEPRECATED_STATE_DOCKED = DeprecatedConstantEnum(VacuumActivity.DOCKED, "2025.12")
_DEPRECATED_STATE_RETURNING = DeprecatedConstantEnum(
VacuumActivity.RETURNING, "2025.12"
)
_DEPRECATED_STATE_ERROR = DeprecatedConstantEnum(VacuumActivity.ERROR, "2025.12")
STATES = [cls.value for cls in VacuumActivity]
# These can be removed if no deprecated constant are in this module anymore
__getattr__ = partial(check_if_deprecated_constant, module_globals=globals())
__dir__ = partial(
dir_with_deprecated_constants, module_globals_keys=[*globals().keys()]
)
__all__ = all_with_deprecated_constants(globals())

View file

@ -20,7 +20,7 @@ from homeassistant.helpers import (
from homeassistant.helpers.config_validation import DEVICE_CONDITION_BASE_SCHEMA
from homeassistant.helpers.typing import ConfigType, TemplateVarsType
from . import DOMAIN, STATE_CLEANING, STATE_DOCKED, STATE_RETURNING
from . import DOMAIN, VacuumActivity
CONDITION_TYPES = {"is_cleaning", "is_docked"}
@ -62,9 +62,9 @@ def async_condition_from_config(
) -> condition.ConditionCheckerType:
"""Create a function to test a device condition."""
if config[CONF_TYPE] == "is_docked":
test_states = [STATE_DOCKED]
test_states = [VacuumActivity.DOCKED]
else:
test_states = [STATE_CLEANING, STATE_RETURNING]
test_states = [VacuumActivity.CLEANING, VacuumActivity.RETURNING]
registry = er.async_get(hass)
entity_id = er.async_resolve_entity_id(registry, config[CONF_ENTITY_ID])

View file

@ -19,7 +19,7 @@ from homeassistant.helpers import config_validation as cv, entity_registry as er
from homeassistant.helpers.trigger import TriggerActionType, TriggerInfo
from homeassistant.helpers.typing import ConfigType
from . import DOMAIN, STATE_CLEANING, STATE_DOCKED
from . import DOMAIN, VacuumActivity
TRIGGER_TYPES = {"cleaning", "docked"}
@ -77,9 +77,9 @@ async def async_attach_trigger(
) -> CALLBACK_TYPE:
"""Attach a trigger."""
if config[CONF_TYPE] == "cleaning":
to_state = STATE_CLEANING
to_state = VacuumActivity.CLEANING
else:
to_state = STATE_DOCKED
to_state = VacuumActivity.DOCKED
state_config = {
CONF_PLATFORM: "state",

View file

@ -11,10 +11,8 @@ from homeassistant.const import (
ATTR_ENTITY_ID,
SERVICE_TURN_OFF,
SERVICE_TURN_ON,
STATE_IDLE,
STATE_OFF,
STATE_ON,
STATE_PAUSED,
)
from homeassistant.core import Context, HomeAssistant, State
@ -26,20 +24,18 @@ from . import (
SERVICE_SET_FAN_SPEED,
SERVICE_START,
SERVICE_STOP,
STATE_CLEANING,
STATE_DOCKED,
STATE_RETURNING,
VacuumActivity,
)
_LOGGER = logging.getLogger(__name__)
VALID_STATES_TOGGLE = {STATE_ON, STATE_OFF}
VALID_STATES_STATE = {
STATE_CLEANING,
STATE_DOCKED,
STATE_IDLE,
STATE_PAUSED,
STATE_RETURNING,
VacuumActivity.CLEANING,
VacuumActivity.DOCKED,
VacuumActivity.IDLE,
VacuumActivity.PAUSED,
VacuumActivity.RETURNING,
}
@ -75,13 +71,13 @@ async def _async_reproduce_state(
service = SERVICE_TURN_ON
elif state.state == STATE_OFF:
service = SERVICE_TURN_OFF
elif state.state == STATE_CLEANING:
elif state.state == VacuumActivity.CLEANING:
service = SERVICE_START
elif state.state in [STATE_DOCKED, STATE_RETURNING]:
elif state.state in [VacuumActivity.DOCKED, VacuumActivity.RETURNING]:
service = SERVICE_RETURN_TO_BASE
elif state.state == STATE_IDLE:
elif state.state == VacuumActivity.IDLE:
service = SERVICE_STOP
elif state.state == STATE_PAUSED:
elif state.state == VacuumActivity.PAUSED:
service = SERVICE_PAUSE
await hass.services.async_call(

View file

@ -10,13 +10,8 @@ from miio import DeviceException
import voluptuous as vol
from homeassistant.components.vacuum import (
STATE_CLEANING,
STATE_DOCKED,
STATE_ERROR,
STATE_IDLE,
STATE_PAUSED,
STATE_RETURNING,
StateVacuumEntity,
VacuumActivity,
VacuumEntityFeature,
)
from homeassistant.config_entries import ConfigEntry
@ -55,29 +50,29 @@ ATTR_ZONE_REPEATER = "repeats"
ATTR_TIMERS = "timers"
STATE_CODE_TO_STATE = {
1: STATE_IDLE, # "Starting"
2: STATE_IDLE, # "Charger disconnected"
3: STATE_IDLE, # "Idle"
4: STATE_CLEANING, # "Remote control active"
5: STATE_CLEANING, # "Cleaning"
6: STATE_RETURNING, # "Returning home"
7: STATE_CLEANING, # "Manual mode"
8: STATE_DOCKED, # "Charging"
9: STATE_ERROR, # "Charging problem"
10: STATE_PAUSED, # "Paused"
11: STATE_CLEANING, # "Spot cleaning"
12: STATE_ERROR, # "Error"
13: STATE_IDLE, # "Shutting down"
14: STATE_DOCKED, # "Updating"
15: STATE_RETURNING, # "Docking"
16: STATE_CLEANING, # "Going to target"
17: STATE_CLEANING, # "Zoned cleaning"
18: STATE_CLEANING, # "Segment cleaning"
22: STATE_DOCKED, # "Emptying the bin" on s7+
23: STATE_DOCKED, # "Washing the mop" on s7maxV
26: STATE_RETURNING, # "Going to wash the mop" on s7maxV
100: STATE_DOCKED, # "Charging complete"
101: STATE_ERROR, # "Device offline"
1: VacuumActivity.IDLE, # "Starting"
2: VacuumActivity.IDLE, # "Charger disconnected"
3: VacuumActivity.IDLE, # "Idle"
4: VacuumActivity.CLEANING, # "Remote control active"
5: VacuumActivity.CLEANING, # "Cleaning"
6: VacuumActivity.RETURNING, # "Returning home"
7: VacuumActivity.CLEANING, # "Manual mode"
8: VacuumActivity.DOCKED, # "Charging"
9: VacuumActivity.ERROR, # "Charging problem"
10: VacuumActivity.PAUSED, # "Paused"
11: VacuumActivity.CLEANING, # "Spot cleaning"
12: VacuumActivity.ERROR, # "Error"
13: VacuumActivity.IDLE, # "Shutting down"
14: VacuumActivity.DOCKED, # "Updating"
15: VacuumActivity.RETURNING, # "Docking"
16: VacuumActivity.CLEANING, # "Going to target"
17: VacuumActivity.CLEANING, # "Zoned cleaning"
18: VacuumActivity.CLEANING, # "Segment cleaning"
22: VacuumActivity.DOCKED, # "Emptying the bin" on s7+
23: VacuumActivity.DOCKED, # "Washing the mop" on s7maxV
26: VacuumActivity.RETURNING, # "Going to wash the mop" on s7maxV
100: VacuumActivity.DOCKED, # "Charging complete"
101: VacuumActivity.ERROR, # "Device offline"
}
@ -211,7 +206,7 @@ class MiroboVacuum(
) -> None:
"""Initialize the Xiaomi vacuum cleaner robot handler."""
super().__init__(device, entry, unique_id, coordinator)
self._state: str | None = None
self._state: VacuumActivity | None = None
async def async_added_to_hass(self) -> None:
"""Run when entity is about to be added to hass."""
@ -219,12 +214,12 @@ class MiroboVacuum(
self._handle_coordinator_update()
@property
def state(self) -> str | None:
def activity(self) -> VacuumActivity | None:
"""Return the status of the vacuum cleaner."""
# The vacuum reverts back to an idle state after erroring out.
# We want to keep returning an error until it has been cleared.
if self.coordinator.data.status.got_error:
return STATE_ERROR
return VacuumActivity.ERROR
return self._state

View file

@ -22,11 +22,7 @@ from homeassistant.components.vacuum import (
DOMAIN as VACUUM_DOMAIN,
SERVICE_SEND_COMMAND,
SERVICE_SET_FAN_SPEED,
STATE_CLEANING,
STATE_DOCKED,
STATE_IDLE,
STATE_PAUSED,
STATE_RETURNING,
VacuumActivity,
)
from homeassistant.const import (
ATTR_ENTITY_ID,
@ -75,35 +71,35 @@ async def test_supported_features(hass: HomeAssistant) -> None:
assert state.attributes.get(ATTR_BATTERY_LEVEL) == 100
assert state.attributes.get(ATTR_FAN_SPEED) == "medium"
assert state.attributes.get(ATTR_FAN_SPEED_LIST) == FAN_SPEEDS
assert state.state == STATE_DOCKED
assert state.state == VacuumActivity.DOCKED
state = hass.states.get(ENTITY_VACUUM_MOST)
assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == 12412
assert state.attributes.get(ATTR_BATTERY_LEVEL) == 100
assert state.attributes.get(ATTR_FAN_SPEED) == "medium"
assert state.attributes.get(ATTR_FAN_SPEED_LIST) == FAN_SPEEDS
assert state.state == STATE_DOCKED
assert state.state == VacuumActivity.DOCKED
state = hass.states.get(ENTITY_VACUUM_BASIC)
assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == 12360
assert state.attributes.get(ATTR_BATTERY_LEVEL) == 100
assert state.attributes.get(ATTR_FAN_SPEED) is None
assert state.attributes.get(ATTR_FAN_SPEED_LIST) is None
assert state.state == STATE_DOCKED
assert state.state == VacuumActivity.DOCKED
state = hass.states.get(ENTITY_VACUUM_MINIMAL)
assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == 3
assert state.attributes.get(ATTR_BATTERY_LEVEL) is None
assert state.attributes.get(ATTR_FAN_SPEED) is None
assert state.attributes.get(ATTR_FAN_SPEED_LIST) is None
assert state.state == STATE_DOCKED
assert state.state == VacuumActivity.DOCKED
state = hass.states.get(ENTITY_VACUUM_NONE)
assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == 0
assert state.attributes.get(ATTR_BATTERY_LEVEL) is None
assert state.attributes.get(ATTR_FAN_SPEED) is None
assert state.attributes.get(ATTR_FAN_SPEED_LIST) is None
assert state.state == STATE_DOCKED
assert state.state == VacuumActivity.DOCKED
async def test_methods(hass: HomeAssistant) -> None:
@ -111,29 +107,29 @@ async def test_methods(hass: HomeAssistant) -> None:
await common.async_start(hass, ENTITY_VACUUM_BASIC)
await hass.async_block_till_done()
state = hass.states.get(ENTITY_VACUUM_BASIC)
assert state.state == STATE_CLEANING
assert state.state == VacuumActivity.CLEANING
await common.async_stop(hass, ENTITY_VACUUM_BASIC)
await hass.async_block_till_done()
state = hass.states.get(ENTITY_VACUUM_BASIC)
assert state.state == STATE_IDLE
assert state.state == VacuumActivity.IDLE
state = hass.states.get(ENTITY_VACUUM_COMPLETE)
await hass.async_block_till_done()
assert state.attributes.get(ATTR_BATTERY_LEVEL) == 100
assert state.state == STATE_DOCKED
assert state.state == VacuumActivity.DOCKED
await async_setup_component(hass, "notify", {})
await hass.async_block_till_done()
await common.async_locate(hass, ENTITY_VACUUM_COMPLETE)
await hass.async_block_till_done()
state = hass.states.get(ENTITY_VACUUM_COMPLETE)
assert state.state == STATE_IDLE
assert state.state == VacuumActivity.IDLE
await common.async_return_to_base(hass, ENTITY_VACUUM_COMPLETE)
await hass.async_block_till_done()
state = hass.states.get(ENTITY_VACUUM_COMPLETE)
assert state.state == STATE_RETURNING
assert state.state == VacuumActivity.RETURNING
await common.async_set_fan_speed(
hass, FAN_SPEEDS[-1], entity_id=ENTITY_VACUUM_COMPLETE
@ -145,21 +141,21 @@ async def test_methods(hass: HomeAssistant) -> None:
await common.async_clean_spot(hass, ENTITY_VACUUM_COMPLETE)
await hass.async_block_till_done()
state = hass.states.get(ENTITY_VACUUM_COMPLETE)
assert state.state == STATE_CLEANING
assert state.state == VacuumActivity.CLEANING
await common.async_pause(hass, ENTITY_VACUUM_COMPLETE)
await hass.async_block_till_done()
state = hass.states.get(ENTITY_VACUUM_COMPLETE)
assert state.state == STATE_PAUSED
assert state.state == VacuumActivity.PAUSED
await common.async_return_to_base(hass, ENTITY_VACUUM_COMPLETE)
state = hass.states.get(ENTITY_VACUUM_COMPLETE)
assert state.state == STATE_RETURNING
assert state.state == VacuumActivity.RETURNING
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=31))
await hass.async_block_till_done()
state = hass.states.get(ENTITY_VACUUM_COMPLETE)
assert state.state == STATE_DOCKED
assert state.state == VacuumActivity.DOCKED
async def test_unsupported_methods(hass: HomeAssistant) -> None:
@ -251,4 +247,4 @@ async def test_send_command(hass: HomeAssistant) -> None:
new_state_complete = hass.states.get(ENTITY_VACUUM_COMPLETE)
assert old_state_complete != new_state_complete
assert new_state_complete.state == STATE_IDLE
assert new_state_complete.state == VacuumActivity.IDLE

View file

@ -431,7 +431,9 @@ async def test_dock_vacuum(hass: HomeAssistant) -> None:
assert helpers.get_google_type(vacuum.DOMAIN, None) is not None
assert trait.DockTrait.supported(vacuum.DOMAIN, 0, None, None)
trt = trait.DockTrait(hass, State("vacuum.bla", vacuum.STATE_IDLE), BASIC_CONFIG)
trt = trait.DockTrait(
hass, State("vacuum.bla", vacuum.VacuumActivity.IDLE), BASIC_CONFIG
)
assert trt.sync_attributes() == {}
@ -454,7 +456,7 @@ async def test_locate_vacuum(hass: HomeAssistant) -> None:
hass,
State(
"vacuum.bla",
vacuum.STATE_IDLE,
vacuum.VacuumActivity.IDLE,
{ATTR_SUPPORTED_FEATURES: VacuumEntityFeature.LOCATE},
),
BASIC_CONFIG,
@ -485,7 +487,7 @@ async def test_energystorage_vacuum(hass: HomeAssistant) -> None:
hass,
State(
"vacuum.bla",
vacuum.STATE_DOCKED,
vacuum.VacuumActivity.DOCKED,
{
ATTR_SUPPORTED_FEATURES: VacuumEntityFeature.BATTERY,
ATTR_BATTERY_LEVEL: 100,
@ -511,7 +513,7 @@ async def test_energystorage_vacuum(hass: HomeAssistant) -> None:
hass,
State(
"vacuum.bla",
vacuum.STATE_CLEANING,
vacuum.VacuumActivity.CLEANING,
{
ATTR_SUPPORTED_FEATURES: VacuumEntityFeature.BATTERY,
ATTR_BATTERY_LEVEL: 20,
@ -551,7 +553,7 @@ async def test_startstop_vacuum(hass: HomeAssistant) -> None:
hass,
State(
"vacuum.bla",
vacuum.STATE_PAUSED,
vacuum.VacuumActivity.PAUSED,
{ATTR_SUPPORTED_FEATURES: VacuumEntityFeature.PAUSE},
),
BASIC_CONFIG,

View file

@ -26,8 +26,7 @@ from homeassistant.components.vacuum import (
SERVICE_START,
SERVICE_TURN_OFF,
SERVICE_TURN_ON,
STATE_CLEANING,
STATE_DOCKED,
VacuumActivity,
VacuumEntityFeature,
)
from homeassistant.const import (
@ -295,7 +294,7 @@ async def test_vacuum_set_state_with_returnhome_and_start_support(
hass.states.async_set(
entity_id,
STATE_CLEANING,
VacuumActivity.CLEANING,
{
ATTR_SUPPORTED_FEATURES: VacuumEntityFeature.RETURN_HOME
| VacuumEntityFeature.START
@ -306,7 +305,7 @@ async def test_vacuum_set_state_with_returnhome_and_start_support(
hass.states.async_set(
entity_id,
STATE_DOCKED,
VacuumActivity.DOCKED,
{
ATTR_SUPPORTED_FEATURES: VacuumEntityFeature.RETURN_HOME
| VacuumEntityFeature.START

View file

@ -9,7 +9,7 @@ from homeassistant.components import litterrobot
from homeassistant.components.vacuum import (
DOMAIN as VACUUM_DOMAIN,
SERVICE_START,
STATE_DOCKED,
VacuumActivity,
)
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import ATTR_ENTITY_ID
@ -30,7 +30,7 @@ async def test_unload_entry(hass: HomeAssistant, mock_account: MagicMock) -> Non
vacuum = hass.states.get(VACUUM_ENTITY_ID)
assert vacuum
assert vacuum.state == STATE_DOCKED
assert vacuum.state == VacuumActivity.DOCKED
await hass.services.async_call(
VACUUM_DOMAIN,

View file

@ -15,9 +15,7 @@ from homeassistant.components.vacuum import (
DOMAIN as PLATFORM_DOMAIN,
SERVICE_START,
SERVICE_STOP,
STATE_DOCKED,
STATE_ERROR,
STATE_PAUSED,
VacuumActivity,
)
from homeassistant.const import ATTR_ENTITY_ID
from homeassistant.core import HomeAssistant
@ -53,7 +51,7 @@ async def test_vacuum(
vacuum = hass.states.get(VACUUM_ENTITY_ID)
assert vacuum
assert vacuum.state == STATE_DOCKED
assert vacuum.state == VacuumActivity.DOCKED
assert vacuum.attributes["is_sleeping"] is False
ent_reg_entry = entity_registry.async_get(VACUUM_ENTITY_ID)
@ -95,18 +93,21 @@ async def test_vacuum_with_error(
vacuum = hass.states.get(VACUUM_ENTITY_ID)
assert vacuum
assert vacuum.state == STATE_ERROR
assert vacuum.state == VacuumActivity.ERROR
@pytest.mark.parametrize(
("robot_data", "expected_state"),
[
({"displayCode": "DC_CAT_DETECT"}, STATE_DOCKED),
({"isDFIFull": True}, STATE_ERROR),
({"robotCycleState": "CYCLE_STATE_CAT_DETECT"}, STATE_PAUSED),
({"displayCode": "DC_CAT_DETECT"}, VacuumActivity.DOCKED),
({"isDFIFull": True}, VacuumActivity.ERROR),
(
{"robotCycleState": "CYCLE_STATE_CAT_DETECT"},
VacuumActivity.PAUSED,
),
],
)
async def test_vacuum_states(
async def test_activitys(
hass: HomeAssistant,
mock_account_with_litterrobot_4: MagicMock,
robot_data: dict[str, str | bool],
@ -150,7 +151,7 @@ async def test_commands(
vacuum = hass.states.get(VACUUM_ENTITY_ID)
assert vacuum
assert vacuum.state == STATE_DOCKED
assert vacuum.state == VacuumActivity.DOCKED
extra = extra or {}
data = {ATTR_ENTITY_ID: VACUUM_ENTITY_ID, **extra.get("data", {})}

View file

@ -27,8 +27,7 @@ from homeassistant.components.vacuum import (
SERVICE_RETURN_TO_BASE,
SERVICE_START,
SERVICE_STOP,
STATE_CLEANING,
STATE_DOCKED,
VacuumActivity,
)
from homeassistant.const import CONF_NAME, ENTITY_MATCH_ALL, STATE_UNKNOWN
from homeassistant.core import HomeAssistant
@ -313,7 +312,7 @@ async def test_status(
}"""
async_fire_mqtt_message(hass, "vacuum/state", message)
state = hass.states.get("vacuum.mqtttest")
assert state.state == STATE_CLEANING
assert state.state == VacuumActivity.CLEANING
assert state.attributes.get(ATTR_BATTERY_LEVEL) == 54
assert state.attributes.get(ATTR_BATTERY_ICON) == "mdi:battery-50"
assert state.attributes.get(ATTR_FAN_SPEED) == "max"
@ -326,7 +325,7 @@ async def test_status(
async_fire_mqtt_message(hass, "vacuum/state", message)
state = hass.states.get("vacuum.mqtttest")
assert state.state == STATE_DOCKED
assert state.state == VacuumActivity.DOCKED
assert state.attributes.get(ATTR_BATTERY_ICON) == "mdi:battery-charging-60"
assert state.attributes.get(ATTR_BATTERY_LEVEL) == 61
assert state.attributes.get(ATTR_FAN_SPEED) == "min"
@ -366,7 +365,7 @@ async def test_no_fan_vacuum(
}"""
async_fire_mqtt_message(hass, "vacuum/state", message)
state = hass.states.get("vacuum.mqtttest")
assert state.state == STATE_CLEANING
assert state.state == VacuumActivity.CLEANING
assert state.attributes.get(ATTR_FAN_SPEED) is None
assert state.attributes.get(ATTR_FAN_SPEED_LIST) is None
assert state.attributes.get(ATTR_BATTERY_LEVEL) == 54
@ -380,7 +379,7 @@ async def test_no_fan_vacuum(
async_fire_mqtt_message(hass, "vacuum/state", message)
state = hass.states.get("vacuum.mqtttest")
assert state.state == STATE_CLEANING
assert state.state == VacuumActivity.CLEANING
assert state.attributes.get(ATTR_FAN_SPEED) is None
assert state.attributes.get(ATTR_FAN_SPEED_LIST) is None
@ -394,7 +393,7 @@ async def test_no_fan_vacuum(
async_fire_mqtt_message(hass, "vacuum/state", message)
state = hass.states.get("vacuum.mqtttest")
assert state.state == STATE_DOCKED
assert state.state == VacuumActivity.DOCKED
assert state.attributes.get(ATTR_BATTERY_ICON) == "mdi:battery-charging-60"
assert state.attributes.get(ATTR_BATTERY_LEVEL) == 61

View file

@ -35,10 +35,7 @@ from homeassistant.components.vacuum import (
SERVICE_SET_FAN_SPEED,
SERVICE_START,
SERVICE_STOP,
STATE_CLEANING,
STATE_IDLE,
STATE_PAUSED,
STATE_RETURNING,
VacuumActivity,
VacuumEntityFeature,
)
from homeassistant.const import (
@ -160,7 +157,7 @@ async def test_simple_properties(
assert entity
assert state
assert state.state == STATE_CLEANING
assert state.state == VacuumActivity.CLEANING
assert entity.unique_id == "AC000Wxxxxxxxxx"
@ -189,10 +186,10 @@ async def test_initial_attributes(
@pytest.mark.parametrize(
("service", "target_state"),
[
(SERVICE_STOP, STATE_IDLE),
(SERVICE_PAUSE, STATE_PAUSED),
(SERVICE_RETURN_TO_BASE, STATE_RETURNING),
(SERVICE_START, STATE_CLEANING),
(SERVICE_STOP, VacuumActivity.IDLE),
(SERVICE_PAUSE, VacuumActivity.PAUSED),
(SERVICE_RETURN_TO_BASE, VacuumActivity.RETURNING),
(SERVICE_START, VacuumActivity.CLEANING),
],
)
async def test_cleaning_states(

View file

@ -3,14 +3,7 @@
import pytest
from homeassistant import setup
from homeassistant.components.vacuum import (
ATTR_BATTERY_LEVEL,
STATE_CLEANING,
STATE_DOCKED,
STATE_IDLE,
STATE_PAUSED,
STATE_RETURNING,
)
from homeassistant.components.vacuum import ATTR_BATTERY_LEVEL, VacuumActivity
from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNAVAILABLE, STATE_UNKNOWN
from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.exceptions import HomeAssistantError
@ -44,7 +37,7 @@ _BATTERY_LEVEL_INPUT_NUMBER = "input_number.battery_level"
},
),
(
STATE_CLEANING,
VacuumActivity.CLEANING,
100,
{
"vacuum": {
@ -149,10 +142,10 @@ async def test_templates_with_entities(hass: HomeAssistant) -> None:
"""Test templates with values from other entities."""
_verify(hass, STATE_UNKNOWN, None)
hass.states.async_set(_STATE_INPUT_SELECT, STATE_CLEANING)
hass.states.async_set(_STATE_INPUT_SELECT, VacuumActivity.CLEANING)
hass.states.async_set(_BATTERY_LEVEL_INPUT_NUMBER, 100)
await hass.async_block_till_done()
_verify(hass, STATE_CLEANING, 100)
_verify(hass, VacuumActivity.CLEANING, 100)
@pytest.mark.parametrize(
@ -370,8 +363,8 @@ async def test_state_services(hass: HomeAssistant, calls: list[ServiceCall]) ->
await hass.async_block_till_done()
# verify
assert hass.states.get(_STATE_INPUT_SELECT).state == STATE_CLEANING
_verify(hass, STATE_CLEANING, None)
assert hass.states.get(_STATE_INPUT_SELECT).state == VacuumActivity.CLEANING
_verify(hass, VacuumActivity.CLEANING, None)
assert len(calls) == 1
assert calls[-1].data["action"] == "start"
assert calls[-1].data["caller"] == _TEST_VACUUM
@ -381,8 +374,8 @@ async def test_state_services(hass: HomeAssistant, calls: list[ServiceCall]) ->
await hass.async_block_till_done()
# verify
assert hass.states.get(_STATE_INPUT_SELECT).state == STATE_PAUSED
_verify(hass, STATE_PAUSED, None)
assert hass.states.get(_STATE_INPUT_SELECT).state == VacuumActivity.PAUSED
_verify(hass, VacuumActivity.PAUSED, None)
assert len(calls) == 2
assert calls[-1].data["action"] == "pause"
assert calls[-1].data["caller"] == _TEST_VACUUM
@ -392,8 +385,8 @@ async def test_state_services(hass: HomeAssistant, calls: list[ServiceCall]) ->
await hass.async_block_till_done()
# verify
assert hass.states.get(_STATE_INPUT_SELECT).state == STATE_IDLE
_verify(hass, STATE_IDLE, None)
assert hass.states.get(_STATE_INPUT_SELECT).state == VacuumActivity.IDLE
_verify(hass, VacuumActivity.IDLE, None)
assert len(calls) == 3
assert calls[-1].data["action"] == "stop"
assert calls[-1].data["caller"] == _TEST_VACUUM
@ -403,8 +396,8 @@ async def test_state_services(hass: HomeAssistant, calls: list[ServiceCall]) ->
await hass.async_block_till_done()
# verify
assert hass.states.get(_STATE_INPUT_SELECT).state == STATE_RETURNING
_verify(hass, STATE_RETURNING, None)
assert hass.states.get(_STATE_INPUT_SELECT).state == VacuumActivity.RETURNING
_verify(hass, VacuumActivity.RETURNING, None)
assert len(calls) == 4
assert calls[-1].data["action"] == "return_to_base"
assert calls[-1].data["caller"] == _TEST_VACUUM
@ -506,7 +499,11 @@ async def _register_basic_vacuum(hass: HomeAssistant) -> None:
assert await setup.async_setup_component(
hass,
"input_select",
{"input_select": {"state": {"name": "State", "options": [STATE_CLEANING]}}},
{
"input_select": {
"state": {"name": "State", "options": [VacuumActivity.CLEANING]}
}
},
)
with assert_setup_component(1, "vacuum"):
@ -522,7 +519,7 @@ async def _register_basic_vacuum(hass: HomeAssistant) -> None:
"service": "input_select.select_option",
"data": {
"entity_id": _STATE_INPUT_SELECT,
"option": STATE_CLEANING,
"option": VacuumActivity.CLEANING,
},
}
}
@ -554,11 +551,11 @@ async def _register_components(hass: HomeAssistant) -> None:
"state": {
"name": "State",
"options": [
STATE_CLEANING,
STATE_DOCKED,
STATE_IDLE,
STATE_PAUSED,
STATE_RETURNING,
VacuumActivity.CLEANING,
VacuumActivity.DOCKED,
VacuumActivity.IDLE,
VacuumActivity.PAUSED,
VacuumActivity.RETURNING,
],
},
"fan_speed": {
@ -578,7 +575,7 @@ async def _register_components(hass: HomeAssistant) -> None:
"service": "input_select.select_option",
"data": {
"entity_id": _STATE_INPUT_SELECT,
"option": STATE_CLEANING,
"option": VacuumActivity.CLEANING,
},
},
{
@ -592,7 +589,10 @@ async def _register_components(hass: HomeAssistant) -> None:
"pause": [
{
"service": "input_select.select_option",
"data": {"entity_id": _STATE_INPUT_SELECT, "option": STATE_PAUSED},
"data": {
"entity_id": _STATE_INPUT_SELECT,
"option": VacuumActivity.PAUSED,
},
},
{
"service": "test.automation",
@ -605,7 +605,10 @@ async def _register_components(hass: HomeAssistant) -> None:
"stop": [
{
"service": "input_select.select_option",
"data": {"entity_id": _STATE_INPUT_SELECT, "option": STATE_IDLE},
"data": {
"entity_id": _STATE_INPUT_SELECT,
"option": VacuumActivity.IDLE,
},
},
{
"service": "test.automation",
@ -620,7 +623,7 @@ async def _register_components(hass: HomeAssistant) -> None:
"service": "input_select.select_option",
"data": {
"entity_id": _STATE_INPUT_SELECT,
"option": STATE_RETURNING,
"option": VacuumActivity.RETURNING,
},
},
{

View file

@ -4,12 +4,8 @@ from typing import Any
from homeassistant.components.vacuum import (
DOMAIN,
STATE_CLEANING,
STATE_DOCKED,
STATE_IDLE,
STATE_PAUSED,
STATE_RETURNING,
StateVacuumEntity,
VacuumActivity,
VacuumEntityFeature,
)
from homeassistant.config_entries import ConfigEntry
@ -39,20 +35,20 @@ class MockVacuum(MockEntity, StateVacuumEntity):
def __init__(self, **values: Any) -> None:
"""Initialize a mock vacuum entity."""
super().__init__(**values)
self._attr_state = STATE_DOCKED
self._attr_activity = VacuumActivity.DOCKED
self._attr_fan_speed = "slow"
def stop(self, **kwargs: Any) -> None:
"""Stop cleaning."""
self._attr_state = STATE_IDLE
self._attr_activity = VacuumActivity.IDLE
def return_to_base(self, **kwargs: Any) -> None:
"""Return to base."""
self._attr_state = STATE_RETURNING
self._attr_activity = VacuumActivity.RETURNING
def clean_spot(self, **kwargs: Any) -> None:
"""Clean a spot."""
self._attr_state = STATE_CLEANING
self._attr_activity = VacuumActivity.CLEANING
def set_fan_speed(self, fan_speed: str, **kwargs: Any) -> None:
"""Set the fan speed."""
@ -60,11 +56,11 @@ class MockVacuum(MockEntity, StateVacuumEntity):
def start(self) -> None:
"""Start cleaning."""
self._attr_state = STATE_CLEANING
self._attr_activity = VacuumActivity.CLEANING
def pause(self) -> None:
"""Pause cleaning."""
self._attr_state = STATE_PAUSED
self._attr_activity = VacuumActivity.PAUSED
async def help_async_setup_entry_init(

View file

@ -1,13 +1,28 @@
"""Fixtures for Vacuum platform tests."""
from collections.abc import Generator
from unittest.mock import MagicMock
import pytest
from homeassistant.config_entries import ConfigFlow
from homeassistant.components.vacuum import DOMAIN as VACUUM_DOMAIN, VacuumEntityFeature
from homeassistant.config_entries import ConfigEntry, ConfigFlow
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from tests.common import mock_config_flow, mock_platform
from . import MockVacuum
from tests.common import (
MockConfigEntry,
MockModule,
MockPlatform,
mock_config_flow,
mock_integration,
mock_platform,
)
TEST_DOMAIN = "test"
class MockFlow(ConfigFlow):
@ -17,7 +32,78 @@ class MockFlow(ConfigFlow):
@pytest.fixture
def config_flow_fixture(hass: HomeAssistant) -> Generator[None]:
"""Mock config flow."""
mock_platform(hass, "test.config_flow")
mock_platform(hass, f"{TEST_DOMAIN}.config_flow")
with mock_config_flow("test", MockFlow):
with mock_config_flow(TEST_DOMAIN, MockFlow):
yield
@pytest.fixture(name="supported_features")
async def vacuum_supported_features() -> VacuumEntityFeature:
"""Return the supported features for the test vacuum entity."""
return (
VacuumEntityFeature.PAUSE
| VacuumEntityFeature.STOP
| VacuumEntityFeature.RETURN_HOME
| VacuumEntityFeature.FAN_SPEED
| VacuumEntityFeature.BATTERY
| VacuumEntityFeature.CLEAN_SPOT
| VacuumEntityFeature.MAP
| VacuumEntityFeature.STATE
| VacuumEntityFeature.START
)
@pytest.fixture(name="mock_vacuum_entity")
async def setup_vacuum_platform_test_entity(
hass: HomeAssistant,
config_flow_fixture: None,
entity_registry: er.EntityRegistry,
supported_features: VacuumEntityFeature,
) -> MagicMock:
"""Set up vacuum entity using an entity platform."""
async def async_setup_entry_init(
hass: HomeAssistant, config_entry: ConfigEntry
) -> bool:
"""Set up test config entry."""
await hass.config_entries.async_forward_entry_setups(
config_entry, [VACUUM_DOMAIN]
)
return True
mock_integration(
hass,
MockModule(
TEST_DOMAIN,
async_setup_entry=async_setup_entry_init,
),
)
entity = MockVacuum(
supported_features=supported_features,
)
async def async_setup_entry_platform(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up test vacuum platform via config entry."""
async_add_entities([entity])
mock_platform(
hass,
f"{TEST_DOMAIN}.{VACUUM_DOMAIN}",
MockPlatform(async_setup_entry=async_setup_entry_platform),
)
config_entry = MockConfigEntry(domain=TEST_DOMAIN)
config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
state = hass.states.get(entity.entity_id)
assert state is not None
return entity

View file

@ -5,12 +5,7 @@ from pytest_unordered import unordered
from homeassistant.components import automation
from homeassistant.components.device_automation import DeviceAutomationType
from homeassistant.components.vacuum import (
DOMAIN,
STATE_CLEANING,
STATE_DOCKED,
STATE_RETURNING,
)
from homeassistant.components.vacuum import DOMAIN, VacuumActivity
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.helpers import device_registry as dr, entity_registry as er
@ -122,7 +117,7 @@ async def test_if_state(
DOMAIN, "test", "5678", device_id=device_entry.id
)
hass.states.async_set(entry.entity_id, STATE_DOCKED)
hass.states.async_set(entry.entity_id, VacuumActivity.DOCKED)
assert await async_setup_component(
hass,
@ -174,7 +169,7 @@ async def test_if_state(
assert len(service_calls) == 1
assert service_calls[0].data["some"] == "is_docked - event - test_event2"
hass.states.async_set(entry.entity_id, STATE_CLEANING)
hass.states.async_set(entry.entity_id, VacuumActivity.CLEANING)
hass.bus.async_fire("test_event1")
hass.bus.async_fire("test_event2")
await hass.async_block_till_done()
@ -182,7 +177,7 @@ async def test_if_state(
assert service_calls[1].data["some"] == "is_cleaning - event - test_event1"
# Returning means it's still cleaning
hass.states.async_set(entry.entity_id, STATE_RETURNING)
hass.states.async_set(entry.entity_id, VacuumActivity.RETURNING)
hass.bus.async_fire("test_event1")
hass.bus.async_fire("test_event2")
await hass.async_block_till_done()
@ -207,7 +202,7 @@ async def test_if_state_legacy(
DOMAIN, "test", "5678", device_id=device_entry.id
)
hass.states.async_set(entry.entity_id, STATE_CLEANING)
hass.states.async_set(entry.entity_id, VacuumActivity.CLEANING)
assert await async_setup_component(
hass,

View file

@ -7,7 +7,7 @@ from pytest_unordered import unordered
from homeassistant.components import automation
from homeassistant.components.device_automation import DeviceAutomationType
from homeassistant.components.vacuum import DOMAIN, STATE_CLEANING, STATE_DOCKED
from homeassistant.components.vacuum import DOMAIN, VacuumActivity
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.helpers import device_registry as dr, entity_registry as er
@ -188,7 +188,7 @@ async def test_if_fires_on_state_change(
DOMAIN, "test", "5678", device_id=device_entry.id
)
hass.states.async_set(entry.entity_id, STATE_DOCKED)
hass.states.async_set(entry.entity_id, VacuumActivity.DOCKED)
assert await async_setup_component(
hass,
@ -238,7 +238,7 @@ async def test_if_fires_on_state_change(
)
# Fake that the entity is cleaning
hass.states.async_set(entry.entity_id, STATE_CLEANING)
hass.states.async_set(entry.entity_id, VacuumActivity.CLEANING)
await hass.async_block_till_done()
assert len(service_calls) == 1
assert (
@ -247,7 +247,7 @@ async def test_if_fires_on_state_change(
)
# Fake that the entity is docked
hass.states.async_set(entry.entity_id, STATE_DOCKED)
hass.states.async_set(entry.entity_id, VacuumActivity.DOCKED)
await hass.async_block_till_done()
assert len(service_calls) == 2
assert (
@ -273,7 +273,7 @@ async def test_if_fires_on_state_change_legacy(
DOMAIN, "test", "5678", device_id=device_entry.id
)
hass.states.async_set(entry.entity_id, STATE_DOCKED)
hass.states.async_set(entry.entity_id, VacuumActivity.DOCKED)
assert await async_setup_component(
hass,
@ -304,7 +304,7 @@ async def test_if_fires_on_state_change_legacy(
)
# Fake that the entity is cleaning
hass.states.async_set(entry.entity_id, STATE_CLEANING)
hass.states.async_set(entry.entity_id, VacuumActivity.CLEANING)
await hass.async_block_till_done()
assert len(service_calls) == 1
assert (
@ -330,7 +330,7 @@ async def test_if_fires_on_state_change_with_for(
DOMAIN, "test", "5678", device_id=device_entry.id
)
hass.states.async_set(entry.entity_id, STATE_DOCKED)
hass.states.async_set(entry.entity_id, VacuumActivity.DOCKED)
assert await async_setup_component(
hass,
@ -365,7 +365,7 @@ async def test_if_fires_on_state_change_with_for(
await hass.async_block_till_done()
assert len(service_calls) == 0
hass.states.async_set(entry.entity_id, STATE_CLEANING)
hass.states.async_set(entry.entity_id, VacuumActivity.CLEANING)
await hass.async_block_till_done()
assert len(service_calls) == 0
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=10))

View file

@ -5,12 +5,13 @@ from __future__ import annotations
from enum import Enum
from types import ModuleType
from typing import Any
from unittest.mock import patch
import pytest
from homeassistant.components import vacuum
from homeassistant.components.vacuum import (
DOMAIN,
DOMAIN as VACUUM_DOMAIN,
SERVICE_CLEAN_SPOT,
SERVICE_LOCATE,
SERVICE_PAUSE,
@ -19,19 +20,19 @@ from homeassistant.components.vacuum import (
SERVICE_SET_FAN_SPEED,
SERVICE_START,
SERVICE_STOP,
STATE_CLEANING,
STATE_IDLE,
STATE_PAUSED,
STATE_RETURNING,
StateVacuumEntity,
VacuumActivity,
VacuumEntityFeature,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers import frame
from . import MockVacuum, help_async_setup_entry_init, help_async_unload_entry
from .common import async_start
from tests.common import (
MockConfigEntry,
MockEntity,
MockModule,
help_test_all,
import_and_test_deprecated_constant_enum,
@ -72,14 +73,33 @@ def test_deprecated_constants(
)
@pytest.mark.parametrize(
("enum", "constant_prefix"), _create_tuples(vacuum.VacuumActivity, "STATE_")
)
@pytest.mark.parametrize(
"module",
[vacuum],
)
def test_deprecated_constants_for_state(
caplog: pytest.LogCaptureFixture,
enum: Enum,
constant_prefix: str,
module: ModuleType,
) -> None:
"""Test deprecated constants."""
import_and_test_deprecated_constant_enum(
caplog, module, enum, constant_prefix, "2025.12"
)
@pytest.mark.parametrize(
("service", "expected_state"),
[
(SERVICE_CLEAN_SPOT, STATE_CLEANING),
(SERVICE_PAUSE, STATE_PAUSED),
(SERVICE_RETURN_TO_BASE, STATE_RETURNING),
(SERVICE_START, STATE_CLEANING),
(SERVICE_STOP, STATE_IDLE),
(SERVICE_CLEAN_SPOT, VacuumActivity.CLEANING),
(SERVICE_PAUSE, VacuumActivity.PAUSED),
(SERVICE_RETURN_TO_BASE, VacuumActivity.RETURNING),
(SERVICE_START, VacuumActivity.CLEANING),
(SERVICE_STOP, VacuumActivity.IDLE),
],
)
async def test_state_services(
@ -101,18 +121,20 @@ async def test_state_services(
async_unload_entry=help_async_unload_entry,
),
)
setup_test_component_platform(hass, DOMAIN, [mock_vacuum], from_config_entry=True)
setup_test_component_platform(
hass, VACUUM_DOMAIN, [mock_vacuum], from_config_entry=True
)
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.services.async_call(
DOMAIN,
VACUUM_DOMAIN,
service,
{"entity_id": mock_vacuum.entity_id},
blocking=True,
)
vacuum_state = hass.states.get(mock_vacuum.entity_id)
activity = hass.states.get(mock_vacuum.entity_id)
assert vacuum_state.state == expected_state
assert activity.state == expected_state
async def test_fan_speed(hass: HomeAssistant, config_flow_fixture: None) -> None:
@ -132,14 +154,16 @@ async def test_fan_speed(hass: HomeAssistant, config_flow_fixture: None) -> None
async_unload_entry=help_async_unload_entry,
),
)
setup_test_component_platform(hass, DOMAIN, [mock_vacuum], from_config_entry=True)
setup_test_component_platform(
hass, VACUUM_DOMAIN, [mock_vacuum], from_config_entry=True
)
assert await hass.config_entries.async_setup(config_entry.entry_id)
config_entry = MockConfigEntry(domain="test", data={})
config_entry.add_to_hass(hass)
await hass.services.async_call(
DOMAIN,
VACUUM_DOMAIN,
SERVICE_SET_FAN_SPEED,
{"entity_id": mock_vacuum.entity_id, "fan_speed": "high"},
blocking=True,
@ -178,11 +202,13 @@ async def test_locate(hass: HomeAssistant, config_flow_fixture: None) -> None:
async_unload_entry=help_async_unload_entry,
),
)
setup_test_component_platform(hass, DOMAIN, [mock_vacuum], from_config_entry=True)
setup_test_component_platform(
hass, VACUUM_DOMAIN, [mock_vacuum], from_config_entry=True
)
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.services.async_call(
DOMAIN,
VACUUM_DOMAIN,
SERVICE_LOCATE,
{"entity_id": mock_vacuum.entity_id},
blocking=True,
@ -227,11 +253,13 @@ async def test_send_command(hass: HomeAssistant, config_flow_fixture: None) -> N
async_unload_entry=help_async_unload_entry,
),
)
setup_test_component_platform(hass, DOMAIN, [mock_vacuum], from_config_entry=True)
setup_test_component_platform(
hass, VACUUM_DOMAIN, [mock_vacuum], from_config_entry=True
)
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.services.async_call(
DOMAIN,
VACUUM_DOMAIN,
SERVICE_SEND_COMMAND,
{
"entity_id": mock_vacuum.entity_id,
@ -278,3 +306,189 @@ async def test_supported_features_compat(hass: HomeAssistant) -> None:
"fan_speed_list": ["silent", "normal", "pet hair"]
}
assert entity._deprecated_supported_features_reported
async def test_vacuum_not_log_deprecated_state_warning(
hass: HomeAssistant,
mock_vacuum_entity: MockVacuum,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test correctly using activity doesn't log issue or raise repair."""
state = hass.states.get(mock_vacuum_entity.entity_id)
assert state is not None
assert (
"should implement the 'activity' property and return its state using the VacuumActivity enum"
not in caplog.text
)
@patch.object(frame, "_REPORTED_INTEGRATIONS", set())
async def test_vacuum_log_deprecated_state_warning_using_state_prop(
hass: HomeAssistant,
config_flow_fixture: None,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test incorrectly using state property does log issue and raise repair."""
class MockLegacyVacuum(MockVacuum):
"""Mocked vacuum entity."""
@property
def state(self) -> str:
"""Return the state of the entity."""
return VacuumActivity.CLEANING
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,
),
)
setup_test_component_platform(hass, VACUUM_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 (
"should implement the 'activity' property and return its state using the VacuumActivity enum"
in caplog.text
)
@patch.object(frame, "_REPORTED_INTEGRATIONS", set())
async def test_vacuum_log_deprecated_state_warning_using_attr_state_attr(
hass: HomeAssistant,
config_flow_fixture: None,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test incorrectly using _attr_state attribute does log issue and raise repair."""
class MockLegacyVacuum(MockVacuum):
"""Mocked vacuum entity."""
def start(self) -> None:
"""Start cleaning."""
self._attr_state = VacuumActivity.CLEANING
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,
),
)
setup_test_component_platform(hass, VACUUM_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 (
"should implement the 'activity' property and return its state using the VacuumActivity enum"
not in caplog.text
)
with patch.object(
MockLegacyVacuum,
"__module__",
"tests.custom_components.test.vacuum",
):
await async_start(hass, entity.entity_id)
assert (
"should implement the 'activity' property and return its state using the VacuumActivity enum"
in caplog.text
)
caplog.clear()
with patch.object(
MockLegacyVacuum,
"__module__",
"tests.custom_components.test.vacuum",
):
await async_start(hass, entity.entity_id)
# Test we only log once
assert (
"should implement the 'activity' property and return its state using the VacuumActivity enum"
not in caplog.text
)
async def test_alarm_control_panel_deprecated_state_does_not_break_state(
hass: HomeAssistant,
config_flow_fixture: None,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test using _attr_state attribute does not break state."""
class MockLegacyVacuum(MockEntity, StateVacuumEntity):
"""Mocked vacuum entity."""
_attr_supported_features = VacuumEntityFeature.STATE | VacuumEntityFeature.START
def __init__(self, **values: Any) -> None:
"""Initialize a mock vacuum entity."""
super().__init__(**values)
self._attr_state = VacuumActivity.DOCKED
def start(self) -> None:
"""Start cleaning."""
self._attr_state = VacuumActivity.CLEANING
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,
),
)
setup_test_component_platform(hass, VACUUM_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 state.state == "docked"
with patch.object(
MockLegacyVacuum,
"__module__",
"tests.custom_components.test.alarm_control_panel",
):
await hass.services.async_call(
VACUUM_DOMAIN,
SERVICE_START,
{
"entity_id": entity.entity_id,
},
blocking=True,
)
await hass.async_block_till_done()
state = hass.states.get(entity.entity_id)
assert state is not None
assert state.state == "cleaning"

View file

@ -9,18 +9,9 @@ from homeassistant.components.vacuum import (
SERVICE_SET_FAN_SPEED,
SERVICE_START,
SERVICE_STOP,
STATE_CLEANING,
STATE_DOCKED,
STATE_RETURNING,
)
from homeassistant.const import (
SERVICE_TURN_OFF,
SERVICE_TURN_ON,
STATE_IDLE,
STATE_OFF,
STATE_ON,
STATE_PAUSED,
VacuumActivity,
)
from homeassistant.const import SERVICE_TURN_OFF, SERVICE_TURN_ON, STATE_OFF, STATE_ON
from homeassistant.core import HomeAssistant, State
from homeassistant.helpers.state import async_reproduce_state
@ -39,11 +30,11 @@ async def test_reproducing_states(
hass.states.async_set(
"vacuum.entity_on_fan", STATE_ON, {ATTR_FAN_SPEED: FAN_SPEED_LOW}
)
hass.states.async_set("vacuum.entity_cleaning", STATE_CLEANING, {})
hass.states.async_set("vacuum.entity_docked", STATE_DOCKED, {})
hass.states.async_set("vacuum.entity_idle", STATE_IDLE, {})
hass.states.async_set("vacuum.entity_returning", STATE_RETURNING, {})
hass.states.async_set("vacuum.entity_paused", STATE_PAUSED, {})
hass.states.async_set("vacuum.entity_cleaning", VacuumActivity.CLEANING, {})
hass.states.async_set("vacuum.entity_docked", VacuumActivity.DOCKED, {})
hass.states.async_set("vacuum.entity_idle", VacuumActivity.IDLE, {})
hass.states.async_set("vacuum.entity_returning", VacuumActivity.RETURNING, {})
hass.states.async_set("vacuum.entity_paused", VacuumActivity.PAUSED, {})
turn_on_calls = async_mock_service(hass, "vacuum", SERVICE_TURN_ON)
turn_off_calls = async_mock_service(hass, "vacuum", SERVICE_TURN_OFF)
@ -60,11 +51,11 @@ async def test_reproducing_states(
State("vacuum.entity_off", STATE_OFF),
State("vacuum.entity_on", STATE_ON),
State("vacuum.entity_on_fan", STATE_ON, {ATTR_FAN_SPEED: FAN_SPEED_LOW}),
State("vacuum.entity_cleaning", STATE_CLEANING),
State("vacuum.entity_docked", STATE_DOCKED),
State("vacuum.entity_idle", STATE_IDLE),
State("vacuum.entity_returning", STATE_RETURNING),
State("vacuum.entity_paused", STATE_PAUSED),
State("vacuum.entity_cleaning", VacuumActivity.CLEANING),
State("vacuum.entity_docked", VacuumActivity.DOCKED),
State("vacuum.entity_idle", VacuumActivity.IDLE),
State("vacuum.entity_returning", VacuumActivity.RETURNING),
State("vacuum.entity_paused", VacuumActivity.PAUSED),
],
)
@ -95,11 +86,11 @@ async def test_reproducing_states(
State("vacuum.entity_off", STATE_ON),
State("vacuum.entity_on", STATE_OFF),
State("vacuum.entity_on_fan", STATE_ON, {ATTR_FAN_SPEED: FAN_SPEED_HIGH}),
State("vacuum.entity_cleaning", STATE_PAUSED),
State("vacuum.entity_docked", STATE_CLEANING),
State("vacuum.entity_idle", STATE_DOCKED),
State("vacuum.entity_returning", STATE_CLEANING),
State("vacuum.entity_paused", STATE_IDLE),
State("vacuum.entity_cleaning", VacuumActivity.PAUSED),
State("vacuum.entity_docked", VacuumActivity.CLEANING),
State("vacuum.entity_idle", VacuumActivity.DOCKED),
State("vacuum.entity_returning", VacuumActivity.CLEANING),
State("vacuum.entity_paused", VacuumActivity.IDLE),
# Should not raise
State("vacuum.non_existing", STATE_ON),
],

View file

@ -21,8 +21,7 @@ from homeassistant.components.vacuum import (
SERVICE_SET_FAN_SPEED,
SERVICE_START,
SERVICE_STOP,
STATE_CLEANING,
STATE_ERROR,
VacuumActivity,
)
from homeassistant.components.xiaomi_miio.const import (
CONF_FLOW_TYPE,
@ -264,7 +263,7 @@ async def test_xiaomi_vacuum_services(
# Check state attributes
state = hass.states.get(entity_id)
assert state.state == STATE_ERROR
assert state.state == VacuumActivity.ERROR
assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == 14204
assert state.attributes.get(ATTR_ERROR) == "Error message"
assert state.attributes.get(ATTR_BATTERY_ICON) == "mdi:battery-80"
@ -450,7 +449,7 @@ async def test_xiaomi_specific_services(
# Check state attributes
state = hass.states.get(entity_id)
assert state.state == STATE_CLEANING
assert state.state == VacuumActivity.CLEANING
assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == 14204
assert state.attributes.get(ATTR_ERROR) is None
assert state.attributes.get(ATTR_BATTERY_ICON) == "mdi:battery-30"