Skip to content

Instantly share code, notes, and snippets.

@Soltroy
Created August 28, 2019 13:17
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save Soltroy/b3ae9a48f4030e3d00fda22af52723d8 to your computer and use it in GitHub Desktop.
Save Soltroy/b3ae9a48f4030e3d00fda22af52723d8 to your computer and use it in GitHub Desktop.
HomeAssistant - EcoVacs Workaround
"""Support for Ecovacs Deebot vacuums."""
import logging
import random
import string
import voluptuous as vol
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, EVENT_HOMEASSISTANT_STOP
from homeassistant.helpers import discovery
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
DOMAIN = "deebot"
CONF_COUNTRY = "country"
CONF_CONTINENT = "continent"
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),
}
)
},
extra=vol.ALLOW_EXTRA,
)
ECOVACS_DEVICES = "ecovacs_devices"
# Generate a random device ID on each bootup
ECOVACS_API_DEVICEID = "".join(
random.choice(string.ascii_uppercase + string.digits) for _ in range(8)
)
def setup(hass, config):
"""Set up the Ecovacs component."""
_LOGGER.debug("Creating new Ecovacs component")
hass.data[ECOVACS_DEVICES] = []
from ozmo import EcoVacsAPI, VacBot
ecovacs_api = EcoVacsAPI(
ECOVACS_API_DEVICEID,
config[DOMAIN].get(CONF_USERNAME),
EcoVacsAPI.md5(config[DOMAIN].get(CONF_PASSWORD)),
config[DOMAIN].get(CONF_COUNTRY),
config[DOMAIN].get(CONF_CONTINENT),
)
devices = ecovacs_api.devices()
_LOGGER.debug("Ecobot devices: %s", devices)
for device in devices:
_LOGGER.info(
"Discovered Ecovacs device on account: %s with nickname %s",
device["did"],
device["nick"],
)
vacbot = VacBot(
ecovacs_api.uid,
ecovacs_api.REALM,
ecovacs_api.resource,
ecovacs_api.user_access_token,
device,
config[DOMAIN].get(CONF_CONTINENT).lower(),
monitor=True,
)
hass.data[ECOVACS_DEVICES].append(vacbot)
def stop(event: object) -> None:
"""Shut down open connections to Ecovacs XMPP server."""
for device in hass.data[ECOVACS_DEVICES]:
_LOGGER.info(
"Shutting down connection to Ecovacs device %s", device.vacuum["did"]
)
device.disconnect()
# Listen for HA stop to disconnect.
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop)
if hass.data[ECOVACS_DEVICES]:
_LOGGER.debug("Starting vacuum components")
discovery.load_platform(hass, "vacuum", DOMAIN, {}, config)
return True
#ecovacs:
deebot:
username: !secret Ecovacs_username
password: !secret Ecovacs_password
country: !secret Ecovacs_coutry
continent: !secret Ecovacs_continent
{
"domain": "deebot",
"name": "deebot",
"documentation": "https://www.home-assistant.io/components/deebot",
"requirements": [
"ozmo==1.0"
],
"dependencies": [],
"codeowners": [
"@OverloadUT"
]
}
"""Support for Ecovacs Ecovacs Vaccums."""
import logging
from homeassistant.components.vacuum import (
SUPPORT_BATTERY,
SUPPORT_CLEAN_SPOT,
SUPPORT_FAN_SPEED,
SUPPORT_LOCATE,
SUPPORT_RETURN_HOME,
SUPPORT_SEND_COMMAND,
SUPPORT_STATUS,
SUPPORT_STOP,
SUPPORT_TURN_OFF,
SUPPORT_TURN_ON,
VacuumDevice,
)
from homeassistant.helpers.icon import icon_for_battery_level
from . import ECOVACS_DEVICES
_LOGGER = logging.getLogger(__name__)
SUPPORT_ECOVACS = (
SUPPORT_BATTERY
| SUPPORT_RETURN_HOME
| SUPPORT_CLEAN_SPOT
| SUPPORT_STOP
| SUPPORT_TURN_OFF
| SUPPORT_TURN_ON
| SUPPORT_LOCATE
| SUPPORT_STATUS
| SUPPORT_SEND_COMMAND
| SUPPORT_FAN_SPEED
)
ATTR_ERROR = "error"
ATTR_COMPONENT_PREFIX = "component_"
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Ecovacs vacuums."""
vacuums = []
for device in hass.data[ECOVACS_DEVICES]:
vacuums.append(EcovacsVacuum(device))
_LOGGER.debug("Adding Ecovacs Vacuums to Hass: %s", vacuums)
add_entities(vacuums, True)
class EcovacsVacuum(VacuumDevice):
"""Ecovacs Vacuums such as Deebot."""
def __init__(self, device):
"""Initialize the Ecovacs Vacuum."""
self.device = device
self.device.connect_and_wait_until_ready()
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._error = None
_LOGGER.debug("Vacuum initialized: %s", self.name)
async def async_added_to_hass(self) -> None:
"""Set up the event listeners now that hass is ready."""
self.device.statusEvents.subscribe(lambda _: self.schedule_update_ha_state())
self.device.batteryEvents.subscribe(lambda _: self.schedule_update_ha_state())
self.device.lifespanEvents.subscribe(lambda _: self.schedule_update_ha_state())
self.device.errorEvents.subscribe(self.on_error)
def on_error(self, error):
"""Handle an error event from the robot.
This will not change the entity's state. If the error caused the state
to change, that will come through as a separate on_status event
"""
if error == "no_error":
self._error = None
else:
self._error = error
self.hass.bus.fire(
"ecovacs_error", {"entity_id": self.entity_id, "error": error}
)
self.schedule_update_ha_state()
@property
def should_poll(self) -> bool:
"""Return True if entity has to be polled for state."""
return False
@property
def unique_id(self) -> str:
"""Return an unique ID."""
return self.device.vacuum.get("did", None)
@property
def is_on(self):
"""Return true if vacuum is currently cleaning."""
return self.device.is_cleaning
@property
def is_charging(self):
"""Return true if vacuum is currently charging."""
return self.device.is_charging
@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_ECOVACS
@property
def status(self):
"""Return the status of the vacuum cleaner."""
return self.device.vacuum_status
def return_to_base(self, **kwargs):
"""Set the vacuum cleaner to return to the dock."""
from ozmo import Charge
self.device.run(Charge())
@property
def battery_icon(self):
"""Return the battery icon for the vacuum cleaner."""
return icon_for_battery_level(
battery_level=self.battery_level, charging=self.is_charging
)
@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 * 100
return super().battery_level
@property
def fan_speed(self):
"""Return the fan speed of the vacuum cleaner."""
return self.device.fan_speed
@property
def fan_speed_list(self):
"""Get the list of available fan speed steps of the vacuum cleaner."""
from ozmo import FAN_SPEED_NORMAL, FAN_SPEED_HIGH
return [FAN_SPEED_NORMAL, FAN_SPEED_HIGH]
def turn_on(self, **kwargs):
"""Turn the vacuum on and start cleaning."""
from ozmo import Clean
self.device.run(Clean())
def turn_off(self, **kwargs):
"""Turn the vacuum off stopping the cleaning and returning home."""
self.return_to_base()
def stop(self, **kwargs):
"""Stop the vacuum cleaner."""
from ozmo import Stop
self.device.run(Stop())
def clean_spot(self, **kwargs):
"""Perform a spot clean-up."""
from ozmo import Spot
self.device.run(Spot())
def locate(self, **kwargs):
"""Locate the vacuum cleaner."""
from ozmo import PlaySound
self.device.run(PlaySound())
def set_fan_speed(self, fan_speed, **kwargs):
"""Set fan speed."""
if self.is_on:
from ozmo import Clean
self.device.run(Clean(mode=self.device.clean_status, speed=fan_speed))
def send_command(self, command, params=None, **kwargs):
"""Send a command to a vacuum cleaner."""
from ozmo import VacBotCommand
self.device.run(VacBotCommand(command, params))
@property
def device_state_attributes(self):
"""Return the device-specific state attributes of this vacuum."""
data = {}
data[ATTR_ERROR] = self._error
for key, val in self.device.components.items():
attr_name = ATTR_COMPONENT_PREFIX + key
data[attr_name] = int(val * 100)
return data
@StefanoGiu
Copy link

Hi all. I'm trying the deebot integration. I'm on Windows installation.

I copied the files into: C:\Users--username--\AppData\Roaming.homeassistant\custom_component\deebot

I get the following error in the log:
2020-03-26 19:04:59 ERROR (MainThread) [homeassistant.setup] Setup failed for deebot: Integration not found.

@poldim
Copy link

poldim commented Apr 14, 2020

Missing the s in your folder: custom_components

# ls custom_components/deebot/
__init__.py    __pycache__    manifest.json  vacuum.py```

@ksheyman
Copy link

Hey! This doesn't seem to be working anymore. Is it still working for you?

@StefanoGiu
Copy link

I'm using it every day :)

@Soltroy
Copy link
Author

Soltroy commented Sep 11, 2020

Sorry - haven't used it for 2 Months ....

@poldim
Copy link

poldim commented Sep 17, 2020

Sorry - haven't used it for 2 Months ....

What are you using instead?

@thib5
Copy link

thib5 commented Sep 17, 2020

Hi, for I unknown reason my robot did not update his status until I reboot my home assistant... could it be doable to replace a command ( let's say "STOP" ) with a "refresh" button something that could pull the info or even log out the Ecovacs server and then reload it with a renew info ? so at least I would be able to force a status update

@Soltroy
Copy link
Author

Soltroy commented Sep 25, 2020

Sorry - haven't used it for 2 Months ....

What are you using instead?

I haven't used Automation via HomeAssistant - ATM the vacuum is controlled with the native app.
Yes, this sucks but currently I can't free up time to fix my HA setup ;-(

Addendum: nothings really broken, I just did every HA update since almost a year without doing anything against breaking changes and therefore many things do not work at the moment

@sunsgs
Copy link

sunsgs commented Dec 8, 2020

Cool, working perfect with my deebot 605. Any tip on how to add filters status and send commands (return to dock, start cleaning) in an automation?

@vannetta
Copy link

vannetta commented Sep 13, 2021

@Soltroy I updated __init__.py because isAlive and getchildren are deprecated in Python 3.9
Have you keeping updated the Deebot custom components? My 605 is only working with this...
At the moment with these little changes I keep on working, but I'm not sure I'll alble to do it if something more complicated is needed...
Thank you so much...

@sunsgs
Copy link

sunsgs commented Sep 20, 2021

@Soltroy I updated __init__.py because isAlive and getchildren are deprecated in Python 3.9
Have you keeping updated the Deebot custom components? My 605 is only working with this...
At the moment with these little changes I keep on working, but I'm not sure I'll alble to do it if something more complicated is needed...
Thank you so much...

@vannetta how did u change it?

@vannetta
Copy link

@sunsgs go to the path you find in the log. It should be something like /usr/bin/local/python3.9/size-packages/ozmo/__init__.py
My logs said that the line 700 with isAlive was desprecated and line 800 with getchildren.
You have to change isAlive with is_alive and erase getchildren
You have to enter in HassOS to reach this path

@sunsgs
Copy link

sunsgs commented Sep 20, 2021

@vannetta, got it. how did u access to that folder?
I've tried to access trough ssh but no luck to find /usr/bin/local/python3.9/size-packages/ozmo/init.py.
in bin folder there is no local folder

@vannetta
Copy link

vannetta commented Sep 21, 2021

@sunsgs I don't rimember the right path; it is shown in the log error. In any case you must access via 22222 port because you need to work on the OS

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment