1 """Support for Ecovacs Ecovacs Vacuums."""
3 from __future__
import annotations
5 from collections.abc
import Mapping
7 from typing
import TYPE_CHECKING, Any
9 from deebot_client.capabilities
import Capabilities, DeviceType
10 from deebot_client.device
import Device
11 from deebot_client.events
import BatteryEvent, FanSpeedEvent, RoomsEvent, StateEvent
12 from deebot_client.models
import CleanAction, CleanMode, Room, State
23 StateVacuumEntityDescription,
33 from .
import EcovacsConfigEntry
34 from .const
import DOMAIN
35 from .entity
import EcovacsEntity, EcovacsLegacyEntity
36 from .util
import get_name_key
38 _LOGGER = logging.getLogger(__name__)
41 ATTR_COMPONENT_PREFIX =
"component_"
43 SERVICE_RAW_GET_POSITIONS =
"raw_get_positions"
48 config_entry: EcovacsConfigEntry,
49 async_add_entities: AddEntitiesCallback,
51 """Set up the Ecovacs vacuums."""
53 controller = config_entry.runtime_data
54 vacuums: list[EcovacsVacuum | EcovacsLegacyVacuum] = [
56 for device
in controller.devices
57 if device.capabilities.device_type
is DeviceType.VACUUM
62 _LOGGER.debug(
"Adding Ecovacs Vacuums to Home Assistant: %s", vacuums)
65 platform = entity_platform.async_get_current_platform()
66 platform.async_register_entity_service(
67 SERVICE_RAW_GET_POSITIONS,
69 "async_raw_get_positions",
70 supports_response=SupportsResponse.ONLY,
75 """Legacy Ecovacs vacuums."""
77 _attr_fan_speed_list = [sucks.FAN_SPEED_NORMAL, sucks.FAN_SPEED_HIGH]
78 _attr_supported_features = (
79 VacuumEntityFeature.BATTERY
80 | VacuumEntityFeature.RETURN_HOME
81 | VacuumEntityFeature.CLEAN_SPOT
82 | VacuumEntityFeature.STOP
83 | VacuumEntityFeature.START
84 | VacuumEntityFeature.LOCATE
85 | VacuumEntityFeature.STATE
86 | VacuumEntityFeature.SEND_COMMAND
87 | VacuumEntityFeature.FAN_SPEED
91 """Set up the event listeners now that hass is ready."""
92 self._event_listeners.append(
93 self.
devicedevice.statusEvents.subscribe(
97 self._event_listeners.append(
98 self.
devicedevice.batteryEvents.subscribe(
102 self._event_listeners.append(
103 self.
devicedevice.lifespanEvents.subscribe(
107 self._event_listeners.append(self.
devicedevice.errorEvents.subscribe(self.
on_erroron_error))
110 """Handle an error event from the robot.
112 This will not change the entity's state. If the error caused the state
113 to change, that will come through as a separate on_status event
115 if error
in [
"no_error", sucks.ERROR_CODES[
"100"]]:
118 self.
errorerror = error
120 self.
hasshass.bus.fire(
121 "ecovacs_error", {
"entity_id": self.
entity_identity_id,
"error": error}
127 """Return the state of the vacuum cleaner."""
128 if self.
errorerror
is not None:
131 if self.
devicedevice.is_cleaning:
132 return STATE_CLEANING
134 if self.
devicedevice.is_charging:
137 if self.
devicedevice.vacuum_status == sucks.CLEAN_MODE_STOP:
140 if self.
devicedevice.vacuum_status == sucks.CHARGE_MODE_RETURNING:
141 return STATE_RETURNING
147 """Return the battery level of the vacuum cleaner."""
148 if self.
devicedevice.battery_status
is not None:
149 return self.
devicedevice.battery_status * 100
155 """Return the battery icon for the vacuum cleaner."""
162 """Return the fan speed of the vacuum cleaner."""
163 return self.
devicedevice.fan_speed
167 """Return the device-specific state attributes of this vacuum."""
168 data: dict[str, Any] = {}
169 data[ATTR_ERROR] = self.
errorerror
172 for key, val
in self.
devicedevice.components.items():
173 attr_name = ATTR_COMPONENT_PREFIX + key
174 data[attr_name] =
int(val * 100)
179 """Set the vacuum cleaner to return to the dock."""
183 def start(self, **kwargs: Any) ->
None:
184 """Turn the vacuum on and start cleaning."""
188 def stop(self, **kwargs: Any) ->
None:
189 """Stop the vacuum cleaner."""
194 """Perform a spot clean-up."""
199 """Locate the vacuum cleaner."""
206 self.
devicedevice.
run(sucks.Clean(mode=self.
devicedevice.clean_status, speed=fan_speed))
211 params: dict[str, Any] | list[Any] |
None =
None,
214 """Send a command to a vacuum cleaner."""
215 self.
devicedevice.
run(sucks.VacBotCommand(command, params))
220 """Get bot and chargers positions."""
222 translation_domain=DOMAIN,
223 translation_key=
"vacuum_raw_get_positions_not_supported",
227 _STATE_TO_VACUUM_STATE = {
228 State.IDLE: STATE_IDLE,
229 State.CLEANING: STATE_CLEANING,
230 State.RETURNING: STATE_RETURNING,
231 State.DOCKED: STATE_DOCKED,
232 State.ERROR: STATE_ERROR,
233 State.PAUSED: STATE_PAUSED,
236 _ATTR_ROOMS =
"rooms"
240 EcovacsEntity[Capabilities],
243 """Ecovacs vacuum."""
245 _unrecorded_attributes = frozenset({_ATTR_ROOMS})
247 _attr_supported_features = (
248 VacuumEntityFeature.PAUSE
249 | VacuumEntityFeature.STOP
250 | VacuumEntityFeature.RETURN_HOME
251 | VacuumEntityFeature.BATTERY
252 | VacuumEntityFeature.SEND_COMMAND
253 | VacuumEntityFeature.LOCATE
254 | VacuumEntityFeature.STATE
255 | VacuumEntityFeature.START
259 key=
"vacuum", translation_key=
"vacuum", name=
None
263 """Initialize the vacuum."""
264 super().
__init__(device, device.capabilities)
266 self.
_rooms_rooms: list[Room] = []
268 if fan_speed := self.
_capability_capability.fan_speed:
275 """Set up the event listeners now that hass is ready."""
278 async
def on_battery(event: BatteryEvent) ->
None:
282 async
def on_rooms(event: RoomsEvent) ->
None:
286 async
def on_status(event: StateEvent) ->
None:
295 async
def on_fan_speed(event: FanSpeedEvent) ->
None:
302 self.
_subscribe_subscribe(map_caps.rooms.event, on_rooms)
306 """Return entity specific state attributes.
308 Implemented by platform classes. Convention for attribute names
309 is lowercase snake_case.
311 rooms: dict[str, Any] = {}
312 for room
in self.
_rooms_rooms:
315 room_values = rooms.get(room_name)
316 if room_values
is None:
317 rooms[room_name] = room.id
318 elif isinstance(room_values, list):
319 room_values.append(room.id)
322 rooms[room_name] = [room_values, room.id]
332 await self.
_device_device.execute_command(self.
_capability_capability.fan_speed.set(fan_speed))
335 """Set the vacuum cleaner to return to the dock."""
336 await self.
_device_device.execute_command(self.
_capability_capability.charge.execute())
339 """Stop the vacuum cleaner."""
343 """Pause the vacuum cleaner."""
347 """Start the vacuum cleaner."""
351 await self.
_device_device.execute_command(
352 self.
_capability_capability.clean.action.command(action)
356 """Locate the vacuum cleaner."""
357 await self.
_device_device.execute_command(self.
_capability_capability.play_sound.execute())
362 params: dict[str, Any] | list[Any] |
None =
None,
365 """Send a command to a vacuum cleaner."""
366 _LOGGER.debug(
"async_send_command %s with %s", command, params)
369 elif isinstance(params, list):
371 translation_domain=DOMAIN,
372 translation_key=
"vacuum_send_command_params_dict",
375 if command
in [
"spot_area",
"custom_area"]:
378 translation_domain=DOMAIN,
379 translation_key=
"vacuum_send_command_params_required",
380 translation_placeholders={
"command": command},
382 if self.
_capability_capability.clean.action.area
is None:
383 info = self.
_device_device.device_info
384 name = info.get(
"nick", info[
"name"])
386 translation_domain=DOMAIN,
387 translation_key=
"vacuum_send_command_area_not_supported",
388 translation_placeholders={
"name": name},
391 if command
in "spot_area":
392 await self.
_device_device.execute_command(
395 str(params[
"rooms"]),
396 params.get(
"cleanings", 1),
399 elif command ==
"custom_area":
400 await self.
_device_device.execute_command(
402 CleanMode.CUSTOM_AREA,
403 str(params[
"coordinates"]),
404 params.get(
"cleanings", 1),
408 await self.
_device_device.execute_command(
409 self.
_capability_capability.custom.set(command, params)
415 """Get bot and chargers positions."""
416 _LOGGER.debug(
"async_raw_get_positions")
418 if not (map_cap := self.
_capability_capability.map)
or not (
419 position_commands := map_cap.position.get
422 translation_domain=DOMAIN,
423 translation_key=
"vacuum_raw_get_positions_not_supported",
426 return await self.
_device_device.execute_command(position_commands[0])
None _subscribe(self, type[EventT] event_type, Callable[[EventT], Coroutine[Any, Any, None]] callback)
None set_fan_speed(self, str fan_speed, **Any kwargs)
None on_error(self, str error)
dict[str, Any] extra_state_attributes(self)
None locate(self, **Any kwargs)
None async_added_to_hass(self)
None async_raw_get_positions(self)
int|None battery_level(self)
None start(self, **Any kwargs)
None stop(self, **Any kwargs)
None send_command(self, str command, dict[str, Any]|list[Any]|None params=None, **Any kwargs)
None return_to_base(self, **Any kwargs)
None clean_spot(self, **Any kwargs)
None async_stop(self, **Any kwargs)
None async_send_command(self, str command, dict[str, Any]|list[Any]|None params=None, **Any kwargs)
None __init__(self, Device device)
None async_set_fan_speed(self, str fan_speed, **Any kwargs)
tuple _attr_supported_features
Mapping[str, Any]|None extra_state_attributes(self)
None async_locate(self, **Any kwargs)
dict[str, Any] async_raw_get_positions(self)
None async_added_to_hass(self)
None _clean_command(self, CleanAction action)
None async_return_to_base(self, **Any kwargs)
int|None battery_level(self)
None async_write_ha_state(self)
None schedule_update_ha_state(self, bool force_refresh=False)
str get_name_key(Enum enum)
None async_setup_entry(HomeAssistant hass, EcovacsConfigEntry config_entry, AddEntitiesCallback async_add_entities)
str icon_for_battery_level(int|None battery_level=None, bool charging=False)
int run(RuntimeConfig runtime_config)