2021-03-21 17:41:41 +00:00
|
|
|
"""Support for Deebot Vaccums."""
|
2021-08-28 19:21:19 +00:00
|
|
|
import logging
|
|
|
|
from typing import Optional, Dict, Any
|
|
|
|
|
|
|
|
from deebotozmo import (
|
|
|
|
FAN_SPEED_QUIET,
|
|
|
|
FAN_SPEED_NORMAL,
|
|
|
|
FAN_SPEED_MAX,
|
|
|
|
FAN_SPEED_MAXPLUS, VacBot, EventListener,
|
|
|
|
)
|
|
|
|
from homeassistant.core import HomeAssistant
|
2021-03-21 17:41:41 +00:00
|
|
|
from homeassistant.util import slugify
|
|
|
|
|
2021-08-28 19:21:19 +00:00
|
|
|
from .const import *
|
|
|
|
from .helpers import get_device_info
|
2021-03-21 17:41:41 +00:00
|
|
|
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
SUPPORT_DEEBOT = (
|
2021-08-28 19:21:19 +00:00
|
|
|
SUPPORT_BATTERY
|
|
|
|
| SUPPORT_FAN_SPEED
|
|
|
|
| SUPPORT_LOCATE
|
|
|
|
| SUPPORT_PAUSE
|
|
|
|
| SUPPORT_RETURN_HOME
|
|
|
|
| SUPPORT_SEND_COMMAND
|
|
|
|
| SUPPORT_START
|
|
|
|
| SUPPORT_STATE
|
2021-03-21 17:41:41 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
|
2021-08-28 19:21:19 +00:00
|
|
|
async def async_setup_entry(hass, config_entry, async_add_devices):
|
|
|
|
"""Add sensors for passed config_entry in HA."""
|
|
|
|
hub = hass.data[DOMAIN][config_entry.entry_id]
|
2021-03-21 17:41:41 +00:00
|
|
|
|
2021-08-28 19:21:19 +00:00
|
|
|
new_devices = []
|
2021-03-21 17:41:41 +00:00
|
|
|
for vacbot in hub.vacbots:
|
2021-08-28 19:21:19 +00:00
|
|
|
new_devices.append(DeebotVacuum(hass, vacbot))
|
|
|
|
|
|
|
|
if new_devices:
|
|
|
|
async_add_devices(new_devices)
|
|
|
|
|
|
|
|
|
|
|
|
def _unsubscribe_listeners(listeners: [EventListener]):
|
|
|
|
for listener in listeners:
|
|
|
|
listener.unsubscribe()
|
|
|
|
|
2021-03-21 17:41:41 +00:00
|
|
|
|
|
|
|
class DeebotVacuum(VacuumEntity):
|
|
|
|
"""Deebot Vacuums"""
|
|
|
|
|
2021-08-28 19:21:19 +00:00
|
|
|
def __init__(self, hass: HomeAssistant, vacbot: VacBot):
|
2021-03-21 17:41:41 +00:00
|
|
|
"""Initialize the Deebot Vacuum."""
|
|
|
|
self._hass = hass
|
|
|
|
self.device = vacbot
|
|
|
|
|
|
|
|
if self.device.vacuum.get("nick", None) is not None:
|
|
|
|
self._name = "{}".format(self.device.vacuum["nick"])
|
|
|
|
else:
|
|
|
|
# In case there is no nickname defined, use the device id
|
|
|
|
self._name = "{}".format(self.device.vacuum["did"])
|
|
|
|
|
|
|
|
self._fan_speed = None
|
|
|
|
self._live_map = None
|
2021-08-28 19:21:19 +00:00
|
|
|
|
|
|
|
self.att_data = {}
|
|
|
|
|
|
|
|
async def async_added_to_hass(self) -> None:
|
|
|
|
"""Set up the event listeners now that hass is ready."""
|
|
|
|
listeners = [
|
|
|
|
self.device.statusEvents.subscribe(lambda _: self.schedule_update_ha_state()),
|
|
|
|
self.device.batteryEvents.subscribe(lambda _: self.schedule_update_ha_state()),
|
|
|
|
self.device.roomEvents.subscribe(lambda _: self.schedule_update_ha_state()),
|
|
|
|
self.device.fanspeedEvents.subscribe(self.on_fan_change)
|
|
|
|
]
|
|
|
|
self.async_on_remove(lambda: _unsubscribe_listeners(listeners))
|
2021-03-21 17:41:41 +00:00
|
|
|
|
|
|
|
def on_fan_change(self, fan_speed):
|
|
|
|
self._fan_speed = fan_speed
|
|
|
|
|
|
|
|
@property
|
|
|
|
def should_poll(self) -> bool:
|
|
|
|
"""Return True if entity has to be polled for state."""
|
2021-08-28 19:21:19 +00:00
|
|
|
return False
|
2021-03-21 17:41:41 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def unique_id(self) -> str:
|
|
|
|
"""Return an unique ID."""
|
|
|
|
return self.device.vacuum.get("did", None)
|
|
|
|
|
|
|
|
@property
|
|
|
|
def name(self):
|
|
|
|
"""Return the name of the device."""
|
|
|
|
return self._name
|
|
|
|
|
|
|
|
@property
|
|
|
|
def supported_features(self):
|
|
|
|
"""Flag vacuum cleaner robot features that are supported."""
|
|
|
|
return SUPPORT_DEEBOT
|
|
|
|
|
|
|
|
@property
|
|
|
|
def state(self):
|
|
|
|
"""Return the state of the vacuum cleaner."""
|
|
|
|
if self.device.vacuum_status is not None and self.device.is_available == True:
|
|
|
|
return STATE_CODE_TO_STATE[self.device.vacuum_status]
|
|
|
|
|
|
|
|
@property
|
|
|
|
def available(self) -> bool:
|
|
|
|
"""Return True if entity is available."""
|
|
|
|
return self.device.is_available
|
|
|
|
|
|
|
|
async def async_return_to_base(self, **kwargs):
|
|
|
|
"""Set the vacuum cleaner to return to the dock."""
|
|
|
|
await self.hass.async_add_executor_job(self.device.Charge)
|
|
|
|
|
|
|
|
@property
|
|
|
|
def battery_level(self):
|
|
|
|
"""Return the battery level of the vacuum cleaner."""
|
|
|
|
if self.device.battery_status is not None:
|
|
|
|
return self.device.battery_status
|
|
|
|
|
|
|
|
return super().battery_level
|
|
|
|
|
|
|
|
@property
|
|
|
|
def fan_speed(self):
|
|
|
|
"""Return the fan speed of the vacuum cleaner."""
|
|
|
|
return self.device.fan_speed
|
|
|
|
|
|
|
|
async def async_set_fan_speed(self, fan_speed, **kwargs):
|
|
|
|
await self.hass.async_add_executor_job(self.device.SetFanSpeed, fan_speed)
|
|
|
|
|
|
|
|
@property
|
|
|
|
def fan_speed_list(self):
|
|
|
|
"""Get the list of available fan speed steps of the vacuum cleaner."""
|
|
|
|
return [FAN_SPEED_QUIET, FAN_SPEED_NORMAL, FAN_SPEED_MAX, FAN_SPEED_MAXPLUS]
|
|
|
|
|
|
|
|
async def async_pause(self):
|
|
|
|
"""Pause the vacuum cleaner."""
|
|
|
|
await self.hass.async_add_executor_job(self.device.CleanPause)
|
|
|
|
|
|
|
|
async def async_start(self):
|
|
|
|
"""Start the vacuum cleaner."""
|
|
|
|
await self.hass.async_add_executor_job(self.device.CleanResume)
|
|
|
|
|
|
|
|
async def async_locate(self, **kwargs):
|
|
|
|
"""Locate the vacuum cleaner."""
|
|
|
|
await self.hass.async_add_executor_job(self.device.PlaySound)
|
|
|
|
|
|
|
|
async def async_send_command(self, command, params=None, **kwargs):
|
|
|
|
"""Send a command to a vacuum cleaner."""
|
|
|
|
_LOGGER.debug("async_send_command %s (%s), %s", command, params, kwargs)
|
|
|
|
|
2021-08-28 19:21:19 +00:00
|
|
|
if command == "spot_area":
|
|
|
|
await self.hass.async_add_executor_job(
|
|
|
|
self.device.SpotArea, params["rooms"], params["cleanings"]
|
|
|
|
)
|
2021-03-21 17:41:41 +00:00
|
|
|
return
|
|
|
|
|
2021-08-28 19:21:19 +00:00
|
|
|
if command == "custom_area":
|
|
|
|
await self.hass.async_add_executor_job(
|
|
|
|
self.device.CustomArea, params["coordinates"], params["cleanings"]
|
|
|
|
)
|
2021-03-21 17:41:41 +00:00
|
|
|
return
|
|
|
|
|
2021-08-28 19:21:19 +00:00
|
|
|
if command == "set_water":
|
|
|
|
await self.hass.async_add_executor_job(
|
|
|
|
self.device.SetWaterLevel, params["amount"]
|
|
|
|
)
|
2021-03-21 17:41:41 +00:00
|
|
|
return
|
|
|
|
|
2021-08-28 19:21:19 +00:00
|
|
|
if command == "relocate":
|
2021-03-21 17:41:41 +00:00
|
|
|
await self.hass.async_add_executor_job(self.device.Relocate)
|
|
|
|
return
|
|
|
|
|
2021-08-28 19:21:19 +00:00
|
|
|
if command == "auto_clean":
|
|
|
|
self.hass.async_add_executor_job(self.device.Clean, params["type"])
|
2021-03-21 17:41:41 +00:00
|
|
|
return
|
|
|
|
|
2021-08-28 19:21:19 +00:00
|
|
|
if command == "refresh_components":
|
2021-03-21 17:41:41 +00:00
|
|
|
await self.hass.async_add_executor_job(self.device.refresh_components)
|
|
|
|
return
|
|
|
|
|
2021-08-28 19:21:19 +00:00
|
|
|
if command == "refresh_statuses":
|
2021-03-21 17:41:41 +00:00
|
|
|
await self.hass.async_add_executor_job(self.device.refresh_statuses)
|
|
|
|
return
|
|
|
|
|
2021-08-28 19:21:19 +00:00
|
|
|
if command == "refresh_live_map":
|
2021-03-21 17:41:41 +00:00
|
|
|
await self.hass.async_add_executor_job(self.device.refresh_liveMap)
|
|
|
|
return
|
|
|
|
|
|
|
|
await self.hass.async_add_executor_job(self.device.exc_command, command, params)
|
|
|
|
|
|
|
|
@property
|
|
|
|
def device_state_attributes(self) -> Optional[Dict[str, Any]]:
|
|
|
|
"""Return device specific state attributes.
|
|
|
|
|
|
|
|
Implemented by platform classes. Convention for attribute names
|
|
|
|
is lowercase snake_case.
|
|
|
|
"""
|
|
|
|
# Needed for custom vacuum-card (https://github.com/denysdovhan/vacuum-card)
|
|
|
|
# Should find a better way without breaking everyone rooms script
|
2021-08-28 19:21:19 +00:00
|
|
|
savedRooms = self.device.getSavedRooms()
|
|
|
|
if savedRooms is not None:
|
|
|
|
self.att_data = {}
|
|
|
|
for r in savedRooms:
|
2021-03-21 17:41:41 +00:00
|
|
|
# convert room name to snake_case to meet the convention
|
|
|
|
room_name = "room_" + slugify(r["subtype"])
|
2021-08-28 19:21:19 +00:00
|
|
|
room_values = self.att_data.get(room_name)
|
2021-03-21 17:41:41 +00:00
|
|
|
if room_values is None:
|
2021-08-28 19:21:19 +00:00
|
|
|
self.att_data[room_name] = r["id"]
|
2021-03-21 17:41:41 +00:00
|
|
|
elif isinstance(room_values, list):
|
|
|
|
room_values.append(r["id"])
|
|
|
|
else:
|
|
|
|
# Convert from int to list
|
2021-08-28 19:21:19 +00:00
|
|
|
self.att_data[room_name] = [room_values, r["id"]]
|
|
|
|
|
|
|
|
if self.device.vacuum_status:
|
|
|
|
self.att_data["status"] = STATE_CODE_TO_STATE[self.device.vacuum_status]
|
2021-03-21 17:41:41 +00:00
|
|
|
|
2021-08-28 19:21:19 +00:00
|
|
|
return self.att_data
|
|
|
|
|
|
|
|
@property
|
|
|
|
def device_info(self) -> Optional[Dict[str, Any]]:
|
|
|
|
return get_device_info(self.device)
|