Update custom components
Este commit está contenido en:
@@ -1,119 +1,62 @@
|
||||
"""Support for Deebot Vaccums."""
|
||||
import asyncio
|
||||
import logging
|
||||
import async_timeout
|
||||
import time
|
||||
import random
|
||||
import string
|
||||
import base64
|
||||
import voluptuous as vol
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from datetime import timedelta
|
||||
from deebotozmo import *
|
||||
from homeassistant.util import Throttle
|
||||
from homeassistant.helpers import discovery
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, EVENT_HOMEASSISTANT_STOP
|
||||
|
||||
REQUIREMENTS = ['deebotozmo==1.7.8']
|
||||
|
||||
CONF_COUNTRY = "country"
|
||||
CONF_CONTINENT = "continent"
|
||||
CONF_DEVICEID = "deviceid"
|
||||
CONF_LIVEMAPPATH = "livemappath"
|
||||
CONF_LIVEMAP = "live_map"
|
||||
CONF_SHOWCOLORROOMS = "show_color_rooms"
|
||||
DEEBOT_DEVICES = "deebot_devices"
|
||||
|
||||
# Generate a random device ID on each bootup
|
||||
DEEBOT_API_DEVICEID = "".join(
|
||||
random.choice(string.ascii_uppercase + string.digits) for _ in range(8)
|
||||
)
|
||||
import asyncio
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from . import hub
|
||||
from .const import DOMAIN, STARTUP
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
HUB = None
|
||||
DOMAIN = 'deebot'
|
||||
PLATFORMS = ["sensor", "binary_sensor", "vacuum", "camera"]
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema({
|
||||
DOMAIN: vol.Schema({
|
||||
vol.Required(CONF_USERNAME): cv.string,
|
||||
vol.Required(CONF_PASSWORD): cv.string,
|
||||
vol.Required(CONF_COUNTRY): vol.All(vol.Lower, cv.string),
|
||||
vol.Required(CONF_CONTINENT): vol.All(vol.Lower, cv.string),
|
||||
vol.Required(CONF_DEVICEID): vol.All(cv.ensure_list, [cv.string]),
|
||||
vol.Optional(CONF_LIVEMAP, default=True): cv.boolean,
|
||||
vol.Optional(CONF_SHOWCOLORROOMS, default=False): cv.boolean,
|
||||
vol.Optional(CONF_LIVEMAPPATH, default='www/'): cv.string
|
||||
}),
|
||||
}, extra=vol.ALLOW_EXTRA)
|
||||
|
||||
def setup(hass, config):
|
||||
"""Set up the Deebot."""
|
||||
global HUB
|
||||
async def async_setup(hass: HomeAssistant, config: dict):
|
||||
"""Set up the Deebot component."""
|
||||
# Ensure our name space for storing objects is a known type. A dict is
|
||||
# common/preferred as it allows a separate instance of your class for each
|
||||
# instance that has been created in the UI.
|
||||
hass.data.setdefault(DOMAIN, {})
|
||||
|
||||
HUB = DeebotHub(config[DOMAIN])
|
||||
|
||||
for component in ('sensor', 'binary_sensor', 'vacuum'):
|
||||
discovery.load_platform(hass, component, DOMAIN, {}, config)
|
||||
# Print startup message
|
||||
_LOGGER.info(STARTUP)
|
||||
|
||||
return True
|
||||
|
||||
class DeebotHub(Entity):
|
||||
"""Deebot Hub"""
|
||||
|
||||
def __init__(self, domain_config):
|
||||
"""Initialize the Deebot Vacuum."""
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
# Store an instance of the "connecting" class that does the work of speaking
|
||||
# with your actual devices.
|
||||
hass.data[DOMAIN][entry.entry_id] = await hass.async_add_executor_job(
|
||||
hub.DeebotHub, hass, entry.data
|
||||
)
|
||||
|
||||
self.config = domain_config
|
||||
self._lock = threading.Lock()
|
||||
|
||||
self.ecovacs_api = EcoVacsAPI(
|
||||
DEEBOT_API_DEVICEID,
|
||||
domain_config.get(CONF_USERNAME),
|
||||
EcoVacsAPI.md5(domain_config.get(CONF_PASSWORD)),
|
||||
domain_config.get(CONF_COUNTRY),
|
||||
domain_config.get(CONF_CONTINENT)
|
||||
)
|
||||
# This creates each HA object for each platform your device requires.
|
||||
# It's done by calling the `async_setup_entry` function in each platform module.
|
||||
for component in PLATFORMS:
|
||||
hass.async_create_task(
|
||||
hass.config_entries.async_forward_entry_setup(entry, component)
|
||||
)
|
||||
|
||||
devices = self.ecovacs_api.devices()
|
||||
liveMapEnabled = domain_config.get(CONF_LIVEMAP)
|
||||
liveMapRooms = domain_config.get(CONF_SHOWCOLORROOMS)
|
||||
country = domain_config.get(CONF_COUNTRY).lower()
|
||||
continent = domain_config.get(CONF_CONTINENT).lower()
|
||||
self.vacbots = []
|
||||
return True
|
||||
|
||||
# CREATE VACBOT FOR EACH DEVICE
|
||||
for device in devices:
|
||||
if device['name'] in domain_config.get(CONF_DEVICEID):
|
||||
vacbot = VacBot(
|
||||
self.ecovacs_api.uid,
|
||||
self.ecovacs_api.resource,
|
||||
self.ecovacs_api.user_access_token,
|
||||
device,
|
||||
country,
|
||||
continent,
|
||||
liveMapEnabled,
|
||||
liveMapRooms
|
||||
)
|
||||
|
||||
_LOGGER.debug("New vacbot found: " + device['name'])
|
||||
|
||||
self.vacbots.append(vacbot)
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
|
||||
"""Unload a config entry."""
|
||||
# This is called when an entry/configured device is to be removed. The class
|
||||
# needs to unload itself, and remove callbacks. See the classes for further
|
||||
# details
|
||||
unload_ok = all(
|
||||
await asyncio.gather(
|
||||
*[
|
||||
hass.config_entries.async_forward_entry_unload(entry, component)
|
||||
for component in PLATFORMS
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
_LOGGER.debug("Hub initialized")
|
||||
if unload_ok:
|
||||
hass.data[DOMAIN][entry.entry_id].disconnect()
|
||||
hass.data[DOMAIN].pop(entry.entry_id)
|
||||
|
||||
@Throttle(timedelta(seconds=10))
|
||||
def update(self):
|
||||
""" Update all statuses. """
|
||||
try:
|
||||
for vacbot in self.vacbots:
|
||||
vacbot.request_all_statuses()
|
||||
except Exception as ex:
|
||||
_LOGGER.error('Update failed: %s', ex)
|
||||
raise
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
""" Return the name of the hub."""
|
||||
return "Deebot Hub"
|
||||
return unload_ok
|
||||
Archivo binario no mostrado.
BIN
custom_components/deebot/__pycache__/__init__.cpython-39.pyc
Archivo normal
BIN
custom_components/deebot/__pycache__/__init__.cpython-39.pyc
Archivo normal
Archivo binario no mostrado.
Archivo binario no mostrado.
BIN
custom_components/deebot/__pycache__/binary_sensor.cpython-39.pyc
Archivo normal
BIN
custom_components/deebot/__pycache__/binary_sensor.cpython-39.pyc
Archivo normal
Archivo binario no mostrado.
BIN
custom_components/deebot/__pycache__/camera.cpython-38.pyc
Archivo normal
BIN
custom_components/deebot/__pycache__/camera.cpython-38.pyc
Archivo normal
Archivo binario no mostrado.
BIN
custom_components/deebot/__pycache__/camera.cpython-39.pyc
Archivo normal
BIN
custom_components/deebot/__pycache__/camera.cpython-39.pyc
Archivo normal
Archivo binario no mostrado.
BIN
custom_components/deebot/__pycache__/config_flow.cpython-38.pyc
Archivo normal
BIN
custom_components/deebot/__pycache__/config_flow.cpython-38.pyc
Archivo normal
Archivo binario no mostrado.
BIN
custom_components/deebot/__pycache__/config_flow.cpython-39.pyc
Archivo normal
BIN
custom_components/deebot/__pycache__/config_flow.cpython-39.pyc
Archivo normal
Archivo binario no mostrado.
BIN
custom_components/deebot/__pycache__/const.cpython-38.pyc
Archivo normal
BIN
custom_components/deebot/__pycache__/const.cpython-38.pyc
Archivo normal
Archivo binario no mostrado.
BIN
custom_components/deebot/__pycache__/const.cpython-39.pyc
Archivo normal
BIN
custom_components/deebot/__pycache__/const.cpython-39.pyc
Archivo normal
Archivo binario no mostrado.
BIN
custom_components/deebot/__pycache__/helpers.cpython-38.pyc
Archivo normal
BIN
custom_components/deebot/__pycache__/helpers.cpython-38.pyc
Archivo normal
Archivo binario no mostrado.
BIN
custom_components/deebot/__pycache__/helpers.cpython-39.pyc
Archivo normal
BIN
custom_components/deebot/__pycache__/helpers.cpython-39.pyc
Archivo normal
Archivo binario no mostrado.
BIN
custom_components/deebot/__pycache__/hub.cpython-38.pyc
Archivo normal
BIN
custom_components/deebot/__pycache__/hub.cpython-38.pyc
Archivo normal
Archivo binario no mostrado.
BIN
custom_components/deebot/__pycache__/hub.cpython-39.pyc
Archivo normal
BIN
custom_components/deebot/__pycache__/hub.cpython-39.pyc
Archivo normal
Archivo binario no mostrado.
Archivo binario no mostrado.
BIN
custom_components/deebot/__pycache__/sensor.cpython-39.pyc
Archivo normal
BIN
custom_components/deebot/__pycache__/sensor.cpython-39.pyc
Archivo normal
Archivo binario no mostrado.
Archivo binario no mostrado.
BIN
custom_components/deebot/__pycache__/vacuum.cpython-39.pyc
Archivo normal
BIN
custom_components/deebot/__pycache__/vacuum.cpython-39.pyc
Archivo normal
Archivo binario no mostrado.
@@ -1,20 +1,25 @@
|
||||
"""Support for Deebot Sensor."""
|
||||
from typing import Optional
|
||||
from typing import Optional, Dict, Any
|
||||
|
||||
from deebotozmo import *
|
||||
from homeassistant.components.binary_sensor import BinarySensorEntity
|
||||
|
||||
from . import HUB as hub
|
||||
from .const import DOMAIN
|
||||
from .helpers import get_device_info
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Set up the Deebot binary sensor platform."""
|
||||
hub.update()
|
||||
async def async_setup_entry(hass, config_entry, async_add_devices):
|
||||
"""Add binary_sensor for passed config_entry in HA."""
|
||||
hub = hass.data[DOMAIN][config_entry.entry_id]
|
||||
|
||||
new_devices = []
|
||||
for vacbot in hub.vacbots:
|
||||
add_devices([DeebotMopAttachedBinarySensor(vacbot, "mop_attached")], True)
|
||||
new_devices.append(DeebotMopAttachedBinarySensor(vacbot, "mop_attached"))
|
||||
|
||||
if new_devices:
|
||||
async_add_devices(new_devices)
|
||||
|
||||
|
||||
class DeebotMopAttachedBinarySensor(BinarySensorEntity):
|
||||
@@ -33,11 +38,20 @@ class DeebotMopAttachedBinarySensor(BinarySensorEntity):
|
||||
|
||||
self._name = self._vacbot_name + "_" + device_id
|
||||
|
||||
@property
|
||||
def unique_id(self) -> str:
|
||||
"""Return an unique ID."""
|
||||
return self._vacbot.vacuum.get("did", None) + "_" + self._id
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the device."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def should_poll(self) -> bool:
|
||||
return False
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
return self._vacbot.mop_attached
|
||||
@@ -46,3 +60,17 @@ class DeebotMopAttachedBinarySensor(BinarySensorEntity):
|
||||
def icon(self) -> Optional[str]:
|
||||
"""Return the icon to use in the frontend, if any."""
|
||||
return "mdi:water" if self.is_on else "mdi:water-off"
|
||||
|
||||
@property
|
||||
def entity_registry_enabled_default(self) -> bool:
|
||||
"""Return if the entity should be enabled when first added to the entity registry."""
|
||||
return True
|
||||
|
||||
@property
|
||||
def device_info(self) -> Optional[Dict[str, Any]]:
|
||||
return get_device_info(self._vacbot)
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Set up the event listeners now that hass is ready."""
|
||||
listener: EventListener = self._vacbot.waterEvents.subscribe(lambda _: self.schedule_update_ha_state())
|
||||
self.async_on_remove(listener.unsubscribe)
|
||||
|
||||
65
custom_components/deebot/camera.py
Archivo normal
65
custom_components/deebot/camera.py
Archivo normal
@@ -0,0 +1,65 @@
|
||||
"""Support for Deebot Vaccums."""
|
||||
import base64
|
||||
import logging
|
||||
from typing import Optional, Dict, Any
|
||||
|
||||
from homeassistant.components.camera import Camera
|
||||
|
||||
from .const import *
|
||||
from .helpers import get_device_info
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
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]
|
||||
|
||||
if hub.liveMapEnabled:
|
||||
new_devices = []
|
||||
|
||||
for vacbot in hub.vacbots:
|
||||
new_devices.append(DeeboLiveCamera(vacbot, "liveMap"))
|
||||
|
||||
if new_devices:
|
||||
async_add_devices(new_devices)
|
||||
|
||||
|
||||
class DeeboLiveCamera(Camera):
|
||||
"""Deebot Live Camera"""
|
||||
|
||||
def __init__(self, vacbot, device_id):
|
||||
"""Initialize the Deebot Vacuum."""
|
||||
super().__init__()
|
||||
|
||||
self._vacbot = vacbot
|
||||
self._id = device_id
|
||||
|
||||
if self._vacbot.vacuum.get("nick", None) is not None:
|
||||
self._vacbot_name = "{}".format(self._vacbot.vacuum["nick"])
|
||||
else:
|
||||
# In case there is no nickname defined, use the device id
|
||||
self._vacbot_name = "{}".format(self._vacbot.vacuum["did"])
|
||||
|
||||
self._name = self._vacbot_name + "_" + device_id
|
||||
|
||||
_LOGGER.debug("Camera initialized: %s", self.name)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the device."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def unique_id(self) -> str:
|
||||
"""Return an unique ID."""
|
||||
return self._vacbot.vacuum.get("did", None) + "_" + self._id
|
||||
|
||||
@property
|
||||
def device_info(self) -> Optional[Dict[str, Any]]:
|
||||
return get_device_info(self._vacbot)
|
||||
|
||||
async def async_camera_image(self):
|
||||
"""Return a still image response from the camera."""
|
||||
|
||||
return base64.decodebytes(self._vacbot.live_map)
|
||||
144
custom_components/deebot/config_flow.py
Archivo normal
144
custom_components/deebot/config_flow.py
Archivo normal
@@ -0,0 +1,144 @@
|
||||
"""Config flow for Deebot integration."""
|
||||
import logging
|
||||
import voluptuous as vol
|
||||
import random
|
||||
import string
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant import config_entries, core, exceptions
|
||||
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
||||
from .const import DOMAIN
|
||||
from .const import *
|
||||
from deebotozmo import EcoVacsAPI, VacBot
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
# Generate a random device ID on each bootup
|
||||
DEEBOT_API_DEVICEID = "".join(
|
||||
random.choice(string.ascii_uppercase + string.digits) for _ in range(8)
|
||||
)
|
||||
|
||||
DATA_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_USERNAME): str,
|
||||
vol.Required(CONF_PASSWORD): str,
|
||||
vol.Required(CONF_COUNTRY): str,
|
||||
vol.Required(CONF_CONTINENT): str,
|
||||
vol.Optional(CONF_LIVEMAP, default=False): bool,
|
||||
vol.Optional(CONF_SHOWCOLORROOMS, default=False): bool,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def validate_input(hass: core.HomeAssistant, data: dict):
|
||||
"""Validate the user input allows us to connect.
|
||||
Data has the keys from DATA_SCHEMA with values provided by the user.
|
||||
"""
|
||||
|
||||
if len(data[CONF_COUNTRY]) != 2:
|
||||
raise InvalidCountry
|
||||
|
||||
if len(data[CONF_CONTINENT]) != 2:
|
||||
raise InvalidContinent
|
||||
|
||||
return await hass.async_add_executor_job(ConfigEntryRetriveRobots, hass, data)
|
||||
|
||||
|
||||
def ConfigEntryRetriveRobots(hass: core.HomeAssistant, domain_config):
|
||||
ecovacs_api = EcoVacsAPI(
|
||||
DEEBOT_API_DEVICEID,
|
||||
domain_config.get(CONF_USERNAME),
|
||||
EcoVacsAPI.md5(domain_config.get(CONF_PASSWORD)),
|
||||
domain_config.get(CONF_COUNTRY),
|
||||
domain_config.get(CONF_CONTINENT),
|
||||
)
|
||||
|
||||
return ecovacs_api.devices()
|
||||
|
||||
|
||||
class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
"""Handle a config flow for Deebot."""
|
||||
|
||||
VERSION = 1
|
||||
CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL
|
||||
|
||||
async def async_step_user(self, user_input=None):
|
||||
"""Handle the initial step."""
|
||||
self.data = {}
|
||||
errors = {}
|
||||
if user_input is not None:
|
||||
try:
|
||||
info = await validate_input(self.hass, user_input)
|
||||
self.robot_list = info
|
||||
except CannotConnect:
|
||||
errors["base"] = "cannot_connect"
|
||||
except InvalidCountry:
|
||||
errors[CONF_COUNTRY] = "invalid_country"
|
||||
except InvalidContinent:
|
||||
errors[CONF_CONTINENT] = "invalid_continent"
|
||||
except Exception:
|
||||
_LOGGER.exception("Unexpected exception")
|
||||
errors["base"] = "unknown"
|
||||
|
||||
if not errors:
|
||||
self.data = user_input
|
||||
|
||||
robot_listDict = {e["name"]: e["nick"] for e in self.robot_list}
|
||||
options_schema = vol.Schema(
|
||||
{
|
||||
vol.Required(
|
||||
CONF_DEVICEID, default=list(robot_listDict.keys())
|
||||
): cv.multi_select(robot_listDict)
|
||||
}
|
||||
)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="robots", data_schema=options_schema, errors=errors
|
||||
)
|
||||
|
||||
# If there is no user input or there were errors, show the form again, including any errors that were found with the input.
|
||||
return self.async_show_form(
|
||||
step_id="user", data_schema=DATA_SCHEMA, errors=errors
|
||||
)
|
||||
|
||||
async def async_step_robots(self, user_input=None):
|
||||
"""Handle the robots selection step."""
|
||||
|
||||
errors = {}
|
||||
if user_input is not None:
|
||||
try:
|
||||
if len(user_input[CONF_DEVICEID]) < 1:
|
||||
errors["base"] = "select_robots"
|
||||
else:
|
||||
self.data[CONF_DEVICEID] = user_input
|
||||
return self.async_create_entry(
|
||||
title=self.data[CONF_USERNAME], data=self.data
|
||||
)
|
||||
except Exception:
|
||||
_LOGGER.exception("Unexpected exception")
|
||||
errors["base"] = "unknown"
|
||||
|
||||
# If there is no user input or there were errors, show the form again, including any errors that were found with the input.
|
||||
robot_listDict = {e["name"]: e["nick"] for e in self.robot_list}
|
||||
options_schema = vol.Schema(
|
||||
{
|
||||
vol.Required(
|
||||
CONF_DEVICEID, default=list(robot_listDict.keys())
|
||||
): cv.multi_select(robot_listDict)
|
||||
}
|
||||
)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="robots", data_schema=options_schema, errors=errors
|
||||
)
|
||||
|
||||
|
||||
class CannotConnect(exceptions.HomeAssistantError):
|
||||
"""Error to indicate we cannot connect."""
|
||||
|
||||
|
||||
class InvalidCountry(exceptions.HomeAssistantError):
|
||||
"""Error to indicate there is an invalid hostname."""
|
||||
|
||||
|
||||
class InvalidContinent(exceptions.HomeAssistantError):
|
||||
"""Error to indicate there is an invalid hostname."""
|
||||
47
custom_components/deebot/const.py
Archivo normal
47
custom_components/deebot/const.py
Archivo normal
@@ -0,0 +1,47 @@
|
||||
from homeassistant.components.vacuum import (
|
||||
PLATFORM_SCHEMA,
|
||||
STATE_CLEANING,
|
||||
STATE_DOCKED,
|
||||
STATE_ERROR,
|
||||
STATE_IDLE,
|
||||
STATE_PAUSED,
|
||||
STATE_RETURNING,
|
||||
SUPPORT_BATTERY,
|
||||
SUPPORT_FAN_SPEED,
|
||||
SUPPORT_LOCATE,
|
||||
SUPPORT_PAUSE,
|
||||
SUPPORT_RETURN_HOME,
|
||||
SUPPORT_SEND_COMMAND,
|
||||
SUPPORT_START,
|
||||
SUPPORT_STATE,
|
||||
VacuumEntity,
|
||||
)
|
||||
|
||||
DOMAIN = "deebot"
|
||||
INTEGRATION_VERSION = "2.1.2"
|
||||
ISSUE_URL = "https://github.com/And3rsL/Deebot-for-Home-Assistant/issues"
|
||||
|
||||
STARTUP = f"""
|
||||
-------------------------------------------------------------------
|
||||
{DOMAIN}
|
||||
Version: {INTEGRATION_VERSION}
|
||||
This is a custom component
|
||||
If you have any issues with this you need to open an issue here:
|
||||
{ISSUE_URL}
|
||||
-------------------------------------------------------------------
|
||||
"""
|
||||
|
||||
CONF_COUNTRY = "country"
|
||||
CONF_CONTINENT = "continent"
|
||||
CONF_DEVICEID = "deviceid"
|
||||
CONF_LIVEMAP = "live_map"
|
||||
CONF_SHOWCOLORROOMS = "show_color_rooms"
|
||||
DEEBOT_DEVICES = f"{DOMAIN}_devices"
|
||||
STATE_CODE_TO_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,
|
||||
}
|
||||
24
custom_components/deebot/helpers.py
Archivo normal
24
custom_components/deebot/helpers.py
Archivo normal
@@ -0,0 +1,24 @@
|
||||
from deebotozmo import VacBot
|
||||
|
||||
from .const import DOMAIN
|
||||
|
||||
|
||||
def get_device_info(vacBot: VacBot):
|
||||
device: dict = vacBot.vacuum
|
||||
identifiers = set()
|
||||
if "did" in device:
|
||||
identifiers.add((DOMAIN, device.get("did")))
|
||||
if "name" in device:
|
||||
identifiers.add((DOMAIN, device.get("name")))
|
||||
|
||||
if not identifiers:
|
||||
# we don't get a identifier to identify the device correctly abort
|
||||
return None
|
||||
|
||||
return {
|
||||
"identifiers": identifiers,
|
||||
"name": device.get("nick", "Deebot vacuum"),
|
||||
"manufacturer": "Ecovacs",
|
||||
"model": device.get("deviceName", "Deebot vacuum"),
|
||||
"sw_version": vacBot.fwversion,
|
||||
}
|
||||
76
custom_components/deebot/hub.py
Archivo normal
76
custom_components/deebot/hub.py
Archivo normal
@@ -0,0 +1,76 @@
|
||||
import logging
|
||||
import random
|
||||
import string
|
||||
import threading
|
||||
|
||||
from deebotozmo import EcoVacsAPI, VacBot
|
||||
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from .const import *
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
# Generate a random device ID on each bootup
|
||||
DEEBOT_API_DEVICEID = "".join(
|
||||
random.choice(string.ascii_uppercase + string.digits) for _ in range(8)
|
||||
)
|
||||
|
||||
|
||||
class DeebotHub:
|
||||
"""Deebot Hub"""
|
||||
|
||||
def __init__(self, hass: HomeAssistant, domain_config):
|
||||
"""Initialize the Deebot Vacuum."""
|
||||
|
||||
self.config = domain_config
|
||||
self._lock = threading.Lock()
|
||||
self.hass = hass
|
||||
|
||||
self.ecovacs_api = EcoVacsAPI(
|
||||
DEEBOT_API_DEVICEID,
|
||||
domain_config.get(CONF_USERNAME),
|
||||
EcoVacsAPI.md5(domain_config.get(CONF_PASSWORD)),
|
||||
domain_config.get(CONF_COUNTRY),
|
||||
domain_config.get(CONF_CONTINENT),
|
||||
)
|
||||
|
||||
devices = self.ecovacs_api.devices()
|
||||
|
||||
liveMapEnabled = domain_config.get(CONF_LIVEMAP)
|
||||
self.liveMapEnabled = liveMapEnabled
|
||||
|
||||
liveMapRooms = domain_config.get(CONF_SHOWCOLORROOMS)
|
||||
country = domain_config.get(CONF_COUNTRY).lower()
|
||||
continent = domain_config.get(CONF_CONTINENT).lower()
|
||||
self.vacbots = []
|
||||
|
||||
# CREATE VACBOT FOR EACH DEVICE
|
||||
for device in devices:
|
||||
if device["name"] in domain_config.get(CONF_DEVICEID)[CONF_DEVICEID]:
|
||||
vacbot = VacBot(
|
||||
self.ecovacs_api.uid,
|
||||
self.ecovacs_api.resource,
|
||||
self.ecovacs_api.user_access_token,
|
||||
device,
|
||||
country,
|
||||
continent,
|
||||
liveMapEnabled,
|
||||
liveMapRooms,
|
||||
)
|
||||
|
||||
_LOGGER.debug("New vacbot found: " + device["name"])
|
||||
vacbot.setScheduleUpdates()
|
||||
|
||||
self.vacbots.append(vacbot)
|
||||
|
||||
_LOGGER.debug("Hub initialized")
|
||||
|
||||
def disconnect(self):
|
||||
for device in self.vacbots:
|
||||
device.disconnect()
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
""" Return the name of the hub."""
|
||||
return "Deebot Hub"
|
||||
@@ -1,11 +1,14 @@
|
||||
{
|
||||
"domain": "deebot",
|
||||
"name": "Deebot for Hassio",
|
||||
"documentation": "https://github.com/And3rsL/Deebot-for-hassio",
|
||||
"name": "Deebot for Home Assistant",
|
||||
"version": "2.1.2",
|
||||
"config_flow": true,
|
||||
"documentation": "https://github.com/And3rsL/Deebot-for-Hassio",
|
||||
"requirements": [
|
||||
"deebotozmo==1.7.8"
|
||||
"deebotozmo==1.8.0"
|
||||
],
|
||||
"dependencies": [],
|
||||
"codeowners": ["@And3rsL"],
|
||||
"homeassistant": "0.110.0"
|
||||
}
|
||||
"codeowners": [
|
||||
"@And3rsL"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,51 +1,43 @@
|
||||
"""Support for Deebot Sensor."""
|
||||
from typing import Optional
|
||||
import logging
|
||||
from typing import Optional, Dict, Any
|
||||
|
||||
from deebotozmo import *
|
||||
from homeassistant.const import (STATE_UNKNOWN)
|
||||
from deebotozmo import (
|
||||
COMPONENT_FILTER,
|
||||
COMPONENT_SIDE_BRUSH,
|
||||
COMPONENT_MAIN_BRUSH, EventListener,
|
||||
)
|
||||
from homeassistant.const import STATE_UNKNOWN
|
||||
from homeassistant.helpers.entity import Entity
|
||||
|
||||
from . import HUB as hub
|
||||
from .const import DOMAIN
|
||||
from .helpers import get_device_info
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
from homeassistant.components.vacuum import (
|
||||
STATE_CLEANING,
|
||||
STATE_DOCKED,
|
||||
STATE_ERROR,
|
||||
STATE_IDLE,
|
||||
STATE_PAUSED,
|
||||
STATE_RETURNING,
|
||||
)
|
||||
|
||||
STATE_CODE_TO_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,
|
||||
}
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Set up the Deebot sensor."""
|
||||
hub.update()
|
||||
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]
|
||||
|
||||
new_devices = []
|
||||
for vacbot in hub.vacbots:
|
||||
# General
|
||||
add_devices([DeebotLastCleanImageSensor(vacbot, "last_clean_image")], True)
|
||||
add_devices([DeebotWaterLevelSensor(vacbot, "water_level")], True)
|
||||
new_devices.append(DeebotLastCleanImageSensor(vacbot, "last_clean_image"))
|
||||
new_devices.append(DeebotWaterLevelSensor(vacbot, "water_level"))
|
||||
|
||||
# Components
|
||||
add_devices([DeebotComponentSensor(vacbot, COMPONENT_MAIN_BRUSH)], True)
|
||||
add_devices([DeebotComponentSensor(vacbot, COMPONENT_SIDE_BRUSH)], True)
|
||||
add_devices([DeebotComponentSensor(vacbot, COMPONENT_FILTER)], True)
|
||||
new_devices.append(DeebotComponentSensor(vacbot, COMPONENT_MAIN_BRUSH))
|
||||
new_devices.append(DeebotComponentSensor(vacbot, COMPONENT_SIDE_BRUSH))
|
||||
new_devices.append(DeebotComponentSensor(vacbot, COMPONENT_FILTER))
|
||||
|
||||
# Stats
|
||||
add_devices([DeebotStatsSensor(vacbot, "stats_area")], True)
|
||||
add_devices([DeebotStatsSensor(vacbot, "stats_time")], True)
|
||||
add_devices([DeebotStatsSensor(vacbot, "stats_type")], True)
|
||||
new_devices.append(DeebotStatsSensor(vacbot, "stats_area"))
|
||||
new_devices.append(DeebotStatsSensor(vacbot, "stats_time"))
|
||||
new_devices.append(DeebotStatsSensor(vacbot, "stats_type"))
|
||||
|
||||
if new_devices:
|
||||
async_add_devices(new_devices)
|
||||
|
||||
|
||||
class DeebotBaseSensor(Entity):
|
||||
@@ -53,7 +45,6 @@ class DeebotBaseSensor(Entity):
|
||||
|
||||
def __init__(self, vacbot, device_id):
|
||||
"""Initialize the Sensor."""
|
||||
|
||||
self._state = STATE_UNKNOWN
|
||||
self._vacbot = vacbot
|
||||
self._id = device_id
|
||||
@@ -71,6 +62,24 @@ class DeebotBaseSensor(Entity):
|
||||
"""Return the name of the device."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def unique_id(self) -> str:
|
||||
"""Return an unique ID."""
|
||||
return self._vacbot.vacuum.get("did", None) + "_" + self._id
|
||||
|
||||
@property
|
||||
def entity_registry_enabled_default(self) -> bool:
|
||||
"""Return if the entity should be enabled when first added to the entity registry."""
|
||||
return True
|
||||
|
||||
@property
|
||||
def should_poll(self) -> bool:
|
||||
return False
|
||||
|
||||
@property
|
||||
def device_info(self) -> Optional[Dict[str, Any]]:
|
||||
return get_device_info(self._vacbot)
|
||||
|
||||
|
||||
class DeebotLastCleanImageSensor(DeebotBaseSensor):
|
||||
"""Deebot Sensor"""
|
||||
@@ -90,6 +99,11 @@ class DeebotLastCleanImageSensor(DeebotBaseSensor):
|
||||
"""Return the icon to use in the frontend, if any."""
|
||||
return "mdi:image-search"
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Set up the event listeners now that hass is ready."""
|
||||
listener: EventListener = self._vacbot.cleanLogsEvents.subscribe(lambda _: self.schedule_update_ha_state())
|
||||
self.async_on_remove(listener.unsubscribe)
|
||||
|
||||
|
||||
class DeebotWaterLevelSensor(DeebotBaseSensor):
|
||||
"""Deebot Sensor"""
|
||||
@@ -110,6 +124,11 @@ class DeebotWaterLevelSensor(DeebotBaseSensor):
|
||||
"""Return the icon to use in the frontend, if any."""
|
||||
return "mdi:water"
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Set up the event listeners now that hass is ready."""
|
||||
listener: EventListener = self._vacbot.waterEvents.subscribe(lambda _: self.schedule_update_ha_state())
|
||||
self.async_on_remove(listener.unsubscribe)
|
||||
|
||||
|
||||
class DeebotComponentSensor(DeebotBaseSensor):
|
||||
"""Deebot Sensor"""
|
||||
@@ -121,7 +140,7 @@ class DeebotComponentSensor(DeebotBaseSensor):
|
||||
@property
|
||||
def unit_of_measurement(self):
|
||||
"""Return the unit of measurement."""
|
||||
return '%'
|
||||
return "%"
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
@@ -139,6 +158,11 @@ class DeebotComponentSensor(DeebotBaseSensor):
|
||||
elif self._id == COMPONENT_FILTER:
|
||||
return "mdi:air-filter"
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Set up the event listeners now that hass is ready."""
|
||||
listener: EventListener = self._vacbot.lifespanEvents.subscribe(lambda _: self.schedule_update_ha_state())
|
||||
self.async_on_remove(listener.unsubscribe)
|
||||
|
||||
|
||||
class DeebotStatsSensor(DeebotBaseSensor):
|
||||
"""Deebot Sensor"""
|
||||
@@ -150,20 +174,20 @@ class DeebotStatsSensor(DeebotBaseSensor):
|
||||
@property
|
||||
def unit_of_measurement(self):
|
||||
"""Return the unit of measurement."""
|
||||
if self._id == 'stats_area':
|
||||
if self._id == "stats_area":
|
||||
return "mq"
|
||||
elif self._id == 'stats_time':
|
||||
elif self._id == "stats_time":
|
||||
return "min"
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
"""Return the state of the vacuum cleaner."""
|
||||
|
||||
if self._id == 'stats_area' and self._vacbot.stats_area is not None:
|
||||
if self._id == "stats_area" and self._vacbot.stats_area is not None:
|
||||
return int(self._vacbot.stats_area)
|
||||
elif self._id == 'stats_time' and self._vacbot.stats_time is not None:
|
||||
return int(self._vacbot.stats_time/60)
|
||||
elif self._id == 'stats_type':
|
||||
elif self._id == "stats_time" and self._vacbot.stats_time is not None:
|
||||
return int(self._vacbot.stats_time / 60)
|
||||
elif self._id == "stats_type":
|
||||
return self._vacbot.stats_type
|
||||
else:
|
||||
return STATE_UNKNOWN
|
||||
@@ -171,9 +195,14 @@ class DeebotStatsSensor(DeebotBaseSensor):
|
||||
@property
|
||||
def icon(self) -> Optional[str]:
|
||||
"""Return the icon to use in the frontend, if any."""
|
||||
if self._id == 'stats_area':
|
||||
if self._id == "stats_area":
|
||||
return "mdi:floor-plan"
|
||||
elif self._id == 'stats_time':
|
||||
elif self._id == "stats_time":
|
||||
return "mdi:timer-outline"
|
||||
elif self._id == 'stats_type':
|
||||
elif self._id == "stats_type":
|
||||
return "mdi:cog"
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Set up the event listeners now that hass is ready."""
|
||||
listener: EventListener = self._vacbot.statsEvents.subscribe(lambda _: self.schedule_update_ha_state())
|
||||
self.async_on_remove(listener.unsubscribe)
|
||||
|
||||
27
custom_components/deebot/translations/de.json
Archivo normal
27
custom_components/deebot/translations/de.json
Archivo normal
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "Bereits konfiguriert"
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "Verbindung fehlgeschlagen",
|
||||
"invalid_auth": "Ung\u00fcltige Authentifizierung",
|
||||
"unknown": "Unerwarteter Fehler",
|
||||
"invalid_country":"Ung\u00fcltiges Land! L\u00e4ndercode sollte aus 2 Zeichen bestehen! Bsp.: de, it, us, ...",
|
||||
"invalid_continent":"Ung\u00fcltiger Kontinent! Code sollte aus 2 Zeichen bestehen! Bsp.: ww, eu, ...",
|
||||
"select_robots": "Bitte w\u00e4hlen Sie mindestens 1 Roboter aus"
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"password": "Passwort",
|
||||
"username": "E-mail oder ShortID",
|
||||
"country": "Land",
|
||||
"continent": "Kontinent",
|
||||
"live_map": "Live-Karte aktiviert",
|
||||
"show_color_rooms": "Raumfarbe anzeigen [BETA]"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
27
custom_components/deebot/translations/en.json
Archivo normal
27
custom_components/deebot/translations/en.json
Archivo normal
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "Alredy configured"
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "Can't connect to the ecovacs API",
|
||||
"invalid_auth": "Invalid username or password",
|
||||
"unknown": "Unknown error",
|
||||
"invalid_country":"Country code should be two letter code, ex: us, uk, etc ",
|
||||
"invalid_continent":"Continent code should be two letter code, ex: ww, eu, etc ",
|
||||
"select_robots": "Please select at least 1 robot"
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"password": "Password",
|
||||
"username": "E-mail or ShortID",
|
||||
"country": "Country",
|
||||
"continent": "Continent",
|
||||
"live_map": "Live Map enabled",
|
||||
"show_color_rooms": "Show room color [BETA]"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
27
custom_components/deebot/translations/it.json
Archivo normal
27
custom_components/deebot/translations/it.json
Archivo normal
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "Già configurato"
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "Non riesco a connettermi con ecovacs API",
|
||||
"invalid_auth": "Username o password errate",
|
||||
"unknown": "Errore sconosciuto",
|
||||
"invalid_country":"Il country code deve essere di due lettere, es: it, us, uk etc ",
|
||||
"invalid_continent":"Il Continent code deve essere di due lettere, es: eu, ww etc ",
|
||||
"select_robots": "Seleziona almeno 1 robot"
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"password": "Password",
|
||||
"username": "E-mail o ShortID",
|
||||
"country": "Country",
|
||||
"continent": "Continent",
|
||||
"live_map": "Abilita Live Map",
|
||||
"show_color_rooms": "Mostra stanze colorate [BETA]"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,79 +1,56 @@
|
||||
"""Support for Deebot Vaccums."""
|
||||
import base64
|
||||
from typing import Optional, Dict, Any, Union, List
|
||||
import logging
|
||||
from typing import Optional, Dict, Any
|
||||
|
||||
from deebotozmo import *
|
||||
from deebotozmo import (
|
||||
FAN_SPEED_QUIET,
|
||||
FAN_SPEED_NORMAL,
|
||||
FAN_SPEED_MAX,
|
||||
FAN_SPEED_MAXPLUS, VacBot, EventListener,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.util import slugify
|
||||
|
||||
from . import HUB as hub
|
||||
|
||||
CONF_COUNTRY = "country"
|
||||
CONF_CONTINENT = "continent"
|
||||
CONF_DEVICEID = "deviceid"
|
||||
CONF_LIVEMAPPATH = "livemappath"
|
||||
CONF_LIVEMAP = "live_map"
|
||||
CONF_SHOWCOLORROOMS = "show_color_rooms"
|
||||
DEEBOT_DEVICES = "deebot_devices"
|
||||
|
||||
from homeassistant.components.vacuum import (
|
||||
PLATFORM_SCHEMA,
|
||||
STATE_CLEANING,
|
||||
STATE_DOCKED,
|
||||
STATE_ERROR,
|
||||
STATE_IDLE,
|
||||
STATE_PAUSED,
|
||||
STATE_RETURNING,
|
||||
SUPPORT_BATTERY,
|
||||
SUPPORT_FAN_SPEED,
|
||||
SUPPORT_LOCATE,
|
||||
SUPPORT_PAUSE,
|
||||
SUPPORT_RETURN_HOME,
|
||||
SUPPORT_SEND_COMMAND,
|
||||
SUPPORT_START,
|
||||
SUPPORT_STATE,
|
||||
VacuumEntity,
|
||||
)
|
||||
from .const import *
|
||||
from .helpers import get_device_info
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
SUPPORT_DEEBOT = (
|
||||
SUPPORT_BATTERY
|
||||
| SUPPORT_FAN_SPEED
|
||||
| SUPPORT_LOCATE
|
||||
| SUPPORT_PAUSE
|
||||
| SUPPORT_RETURN_HOME
|
||||
| SUPPORT_SEND_COMMAND
|
||||
| SUPPORT_START
|
||||
| SUPPORT_STATE
|
||||
SUPPORT_BATTERY
|
||||
| SUPPORT_FAN_SPEED
|
||||
| SUPPORT_LOCATE
|
||||
| SUPPORT_PAUSE
|
||||
| SUPPORT_RETURN_HOME
|
||||
| SUPPORT_SEND_COMMAND
|
||||
| SUPPORT_START
|
||||
| SUPPORT_STATE
|
||||
)
|
||||
|
||||
STATE_CODE_TO_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,
|
||||
}
|
||||
|
||||
ATTR_COMPONENT_PREFIX = "component_"
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Set up the Deebot vacuums."""
|
||||
if DEEBOT_DEVICES not in hass.data:
|
||||
hass.data[DEEBOT_DEVICES] = []
|
||||
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]
|
||||
|
||||
new_devices = []
|
||||
for vacbot in hub.vacbots:
|
||||
vacuum = DeebotVacuum(hass, vacbot)
|
||||
add_devices([vacuum])
|
||||
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()
|
||||
|
||||
|
||||
class DeebotVacuum(VacuumEntity):
|
||||
"""Deebot Vacuums"""
|
||||
|
||||
def __init__(self, hass, vacbot):
|
||||
def __init__(self, hass: HomeAssistant, vacbot: VacBot):
|
||||
"""Initialize the Deebot Vacuum."""
|
||||
self._hass = hass
|
||||
|
||||
self.device = vacbot
|
||||
|
||||
if self.device.vacuum.get("nick", None) is not None:
|
||||
@@ -84,11 +61,18 @@ class DeebotVacuum(VacuumEntity):
|
||||
|
||||
self._fan_speed = None
|
||||
self._live_map = None
|
||||
self._live_map_path = hub.config.get(CONF_LIVEMAPPATH) + self._name + '_liveMap.png'
|
||||
|
||||
self.device.refresh_statuses()
|
||||
|
||||
_LOGGER.debug("Vacuum initialized: %s", self.name)
|
||||
|
||||
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))
|
||||
|
||||
def on_fan_change(self, fan_speed):
|
||||
self._fan_speed = fan_speed
|
||||
@@ -96,7 +80,7 @@ class DeebotVacuum(VacuumEntity):
|
||||
@property
|
||||
def should_poll(self) -> bool:
|
||||
"""Return True if entity has to be polled for state."""
|
||||
return True
|
||||
return False
|
||||
|
||||
@property
|
||||
def unique_id(self) -> str:
|
||||
@@ -165,58 +149,46 @@ class DeebotVacuum(VacuumEntity):
|
||||
"""Send a command to a vacuum cleaner."""
|
||||
_LOGGER.debug("async_send_command %s (%s), %s", command, params, kwargs)
|
||||
|
||||
if command == 'spot_area':
|
||||
await self.hass.async_add_executor_job(self.device.SpotArea, params['rooms'], params['cleanings'])
|
||||
if command == "spot_area":
|
||||
await self.hass.async_add_executor_job(
|
||||
self.device.SpotArea, params["rooms"], params["cleanings"]
|
||||
)
|
||||
return
|
||||
|
||||
if command == 'custom_area':
|
||||
await self.hass.async_add_executor_job(self.device.CustomArea, params['coordinates'], params['cleanings'])
|
||||
if command == "custom_area":
|
||||
await self.hass.async_add_executor_job(
|
||||
self.device.CustomArea, params["coordinates"], params["cleanings"]
|
||||
)
|
||||
return
|
||||
|
||||
if command == 'set_water':
|
||||
await self.hass.async_add_executor_job(self.device.SetWaterLevel, params['amount'])
|
||||
if command == "set_water":
|
||||
await self.hass.async_add_executor_job(
|
||||
self.device.SetWaterLevel, params["amount"]
|
||||
)
|
||||
return
|
||||
|
||||
if command == 'relocate':
|
||||
if command == "relocate":
|
||||
await self.hass.async_add_executor_job(self.device.Relocate)
|
||||
return
|
||||
|
||||
if command == 'auto_clean':
|
||||
self.hass.async_add_executor_job(self.device.Clean, params['type'])
|
||||
if command == "auto_clean":
|
||||
self.hass.async_add_executor_job(self.device.Clean, params["type"])
|
||||
return
|
||||
|
||||
if command == 'refresh_components':
|
||||
if command == "refresh_components":
|
||||
await self.hass.async_add_executor_job(self.device.refresh_components)
|
||||
return
|
||||
|
||||
if command == 'refresh_statuses':
|
||||
if command == "refresh_statuses":
|
||||
await self.hass.async_add_executor_job(self.device.refresh_statuses)
|
||||
return
|
||||
|
||||
if command == 'refresh_live_map':
|
||||
if command == "refresh_live_map":
|
||||
await self.hass.async_add_executor_job(self.device.refresh_liveMap)
|
||||
return
|
||||
|
||||
if command == 'save_live_map':
|
||||
if(self._live_map != self.device.live_map):
|
||||
self._live_map = self.device.live_map
|
||||
with open(params['path'], "wb") as fh:
|
||||
fh.write(base64.decodebytes(self.device.live_map))
|
||||
|
||||
await self.hass.async_add_executor_job(self.device.exc_command, command, params)
|
||||
|
||||
async def async_update(self):
|
||||
"""Fetch state from the device."""
|
||||
await self.hass.async_add_executor_job(self.device.request_all_statuses)
|
||||
|
||||
try:
|
||||
if(self._live_map != self.device.live_map):
|
||||
self._live_map = self.device.live_map
|
||||
with open(self._live_map_path, "wb") as fh:
|
||||
fh.write(base64.decodebytes(self.device.live_map))
|
||||
except KeyError:
|
||||
_LOGGER.warning("Can't access local folder: %s", self._live_map_path)
|
||||
|
||||
@property
|
||||
def device_state_attributes(self) -> Optional[Dict[str, Any]]:
|
||||
"""Return device specific state attributes.
|
||||
@@ -224,24 +196,28 @@ class DeebotVacuum(VacuumEntity):
|
||||
Implemented by platform classes. Convention for attribute names
|
||||
is lowercase snake_case.
|
||||
"""
|
||||
|
||||
data: Dict[str, Union[int, List[int]]] = {}
|
||||
|
||||
# Needed for custom vacuum-card (https://github.com/denysdovhan/vacuum-card)
|
||||
# Should find a better way without breaking everyone rooms script
|
||||
data['status'] = STATE_CODE_TO_STATE[self.device.vacuum_status]
|
||||
|
||||
if self.device.getSavedRooms() is not None:
|
||||
for r in self.device.getSavedRooms():
|
||||
savedRooms = self.device.getSavedRooms()
|
||||
if savedRooms is not None:
|
||||
self.att_data = {}
|
||||
for r in savedRooms:
|
||||
# convert room name to snake_case to meet the convention
|
||||
room_name = "room_" + slugify(r["subtype"])
|
||||
room_values = data.get(room_name)
|
||||
room_values = self.att_data.get(room_name)
|
||||
if room_values is None:
|
||||
data[room_name] = r["id"]
|
||||
self.att_data[room_name] = r["id"]
|
||||
elif isinstance(room_values, list):
|
||||
room_values.append(r["id"])
|
||||
else:
|
||||
# Convert from int to list
|
||||
data[room_name] = [room_values, r["id"]]
|
||||
self.att_data[room_name] = [room_values, r["id"]]
|
||||
|
||||
return data
|
||||
if self.device.vacuum_status:
|
||||
self.att_data["status"] = STATE_CODE_TO_STATE[self.device.vacuum_status]
|
||||
|
||||
return self.att_data
|
||||
|
||||
@property
|
||||
def device_info(self) -> Optional[Dict[str, Any]]:
|
||||
return get_device_info(self.device)
|
||||
|
||||
Referencia en una nueva incidencia
Block a user