add custom component from main branch
This commit is contained in:
@@ -0,0 +1,2 @@
|
|||||||
|
# Auto detect text files and perform LF normalization
|
||||||
|
* text=auto
|
||||||
@@ -1,152 +1,52 @@
|
|||||||
Linux: [](https://travis-ci.org/wpietri/sucks)
|
# Home Assistant Ecovacs Custom Component with Bumper Support
|
||||||
Windows: [](https://ci.appveyor.com/project/wpietri/sucks)
|
Replaces built in ecovacs component.
|
||||||
|
|
||||||
|
Works with bumper with my N79 and should work with at least other XMPP based ecovacs. Don't know if changes will work with MQTT based ones.
|
||||||
|
|
||||||
sucks
|
Added additional catches to sucks because my N79 sends some weird payloads, but attributes all pull in now for brush life spans. Couple initial queries it also sends weird that I'm in process of catching atm. As of version 1.3.0 (in the manifest.json) these initial queries and all attributes are working. Was using an implementation completely mine but saw in the MQTT class there were already catches for child payloads without the main payload having the expected td in its payload. Kept comments in giving credit and adapted them to work with xmpp.
|
||||||
=====
|
|
||||||
|
|
||||||
A simple command-line python script to drive a robot vacuum. Currently
|
With bumper and my N79 commands would work but some queries had responses that included errno='', which bumper would flag as an error even though the full response was there. If your debug logs are throwing errors and the errno is '' then my small fork of bumper may help https://github.com/bittles/bumper-fork
|
||||||
known to work with the Ecovacs Deebot N79, M80 Pro, M81, M88
|
|
||||||
Pro, and R95 MKII from both North America and Europe.
|
|
||||||
|
|
||||||
Does it work for your model as well? Join the discussion on the
|
Should work as regular if bumper isn't used in config but haven't tested yet, goal was to get it all local. Maybe mess around and test it in future.
|
||||||
[sucks-users mailing
|
|
||||||
list](https://groups.google.com/forum/#!forum/sucks-users).
|
|
||||||
|
|
||||||
If you're curious about the protocol, I have [a rough doc](http://github.com/wpietri/sucks/blob/master/protocol.md)
|
I'm using the docker-compose example for bumper by bmartin5692, https://github.com/bmartin5692/bumper on an odroid-n2+.
|
||||||
started. I'll happily accept pull requests for it.
|
|
||||||
|
|
||||||
Why the project name? Well, a) it's ridiculous that I needed to MITM
|
For DNS routing I have an Asus AX88u with asus-merlin installed running Adguard. DNS rewrites in AdGuard for domains:
|
||||||
my own vacuum. This is not the future I signed up for. And b),
|
|
||||||
it's a vacuum.
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
If you have a recent version of Python 3, you should be able to
|
|
||||||
do `pip install sucks` to get the most recently released version of
|
|
||||||
this.
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
To get started, you'll need to have already set up an EcoVacs account
|
|
||||||
using your smartphone.
|
|
||||||
|
|
||||||
With that ready, step one is to log in:
|
|
||||||
```
|
```
|
||||||
% sucks login
|
*.ecouser.net
|
||||||
Ecovacs app email: [your email]
|
*.ecovacs.com
|
||||||
Ecovacs app password: [your password]
|
*.ecovacs.net
|
||||||
your two-letter country code: us
|
|
||||||
your two-letter continent code: na
|
|
||||||
Config saved.
|
|
||||||
```
|
```
|
||||||
|
pointing to my bumper server.
|
||||||
|
|
||||||
That creates a config file in a platform-appropriate place. The password
|
Big credits to bmartin5692 for his fork of sucks to base this off of as well.
|
||||||
is hashed before saving, so it's reasonably safe. (If it doesn't appear
|
|
||||||
to work for your continent, try "ww", their world-wide catchall.)
|
|
||||||
|
|
||||||
With that set up, you could have it clean in auto mode for 10 minutes
|
## Home Assistant Install & Config
|
||||||
and return to its charger:
|
Drop the ecovacs folder into your custom_components folder. If I polish this up I'll add hacs support.
|
||||||
|
Restart HASS.
|
||||||
|
|
||||||
|
In your configuration.yaml:
|
||||||
```
|
```
|
||||||
% sucks clean 10
|
ecovacs:
|
||||||
|
username:
|
||||||
|
password:
|
||||||
|
country:
|
||||||
|
continent:
|
||||||
|
bumper: true/false (optional, defaults false)
|
||||||
|
bumper_server: (optional, defaults null)
|
||||||
|
verify_ssl: true/false, false if using bumper (optional, defaults true)
|
||||||
```
|
```
|
||||||
|
Any username, password, country, and continent should work if bumper is true. Set bumper_server to the ip_address where you're running bumper and set verify_ssl to false for bumper. If you're not using bumper this SHOULD technically work no different than the Home Assistant ecovacs integration but I haven't looked at it enough to be sure and I haven't tested it.
|
||||||
|
|
||||||
You could have it clean for 15 minutes and then do an extra 10 minutes
|
### Example Config
|
||||||
of edging:
|
|
||||||
|
|
||||||
```
|
```
|
||||||
% sucks clean 15 edge 10
|
ecovacs:
|
||||||
|
username: bumper
|
||||||
|
password: bumper
|
||||||
|
country: us
|
||||||
|
continent: na
|
||||||
|
bumper: true
|
||||||
|
bumper_server: "192.168.1.55"
|
||||||
|
verify_ssl: false
|
||||||
```
|
```
|
||||||
|
Just finished getting this working late 12/13/22 so not sure if everything works yet but will commit changes here if I update it or at least document issues.
|
||||||
If you wanted it to clean for 5 minutes and then stop without charging:
|
|
||||||
|
|
||||||
```
|
|
||||||
% sucks clean 5 stop
|
|
||||||
```
|
|
||||||
|
|
||||||
If it's running amok and you'd just like it to stop where it is:
|
|
||||||
|
|
||||||
```
|
|
||||||
% sucks stop
|
|
||||||
```
|
|
||||||
|
|
||||||
To tell it to go plug in:
|
|
||||||
|
|
||||||
```
|
|
||||||
% sucks charge
|
|
||||||
```
|
|
||||||
|
|
||||||
I run mine from my crontab, but I didn't want it to clean every day,
|
|
||||||
so it also has a mode where it randomly decides to run or not based on
|
|
||||||
a frequency you give it. My crontab entry looks like this:
|
|
||||||
|
|
||||||
```
|
|
||||||
0 10 * * * /home/william/projects/sucks/sucks.sh clean -f 4/7 15 edge -f 1/14 10
|
|
||||||
```
|
|
||||||
|
|
||||||
This means that every day at 10 am, it might do something. 4 days out
|
|
||||||
of 7, it will do 15 minutes of automatic cleaning. 1 day out of 14,
|
|
||||||
it will do 10 minutes of edging. And afterward it will always go back to
|
|
||||||
charge.
|
|
||||||
|
|
||||||
## Library use
|
|
||||||
|
|
||||||
You are welcome to try using this as a python library for other efforts. The
|
|
||||||
API is still experimental, so expect changes. Please join the [mailing
|
|
||||||
list](https://groups.google.com/forum/#!forum/sucks-users) to participate in
|
|
||||||
shaping the API.
|
|
||||||
|
|
||||||
A simple usage might go something like this:
|
|
||||||
|
|
||||||
```python
|
|
||||||
from sucks import *
|
|
||||||
|
|
||||||
config = ...
|
|
||||||
|
|
||||||
api = EcoVacsAPI(config['device_id'], config['email'], config['password_hash'],
|
|
||||||
config['country'], config['continent'])
|
|
||||||
my_vac = api.devices()[0]
|
|
||||||
vacbot = VacBot(api.uid, api.REALM, api.resource, api.user_access_token, my_vac, config['continent'])
|
|
||||||
vacbot.connect_and_wait_until_ready()
|
|
||||||
|
|
||||||
vacbot.run(Clean()) # start cleaning
|
|
||||||
time.sleep(900) # clean for 15 minutes
|
|
||||||
vacbot.run(Charge()) # return to the charger
|
|
||||||
```
|
|
||||||
|
|
||||||
## Developing
|
|
||||||
|
|
||||||
If you'd like to join in on developing, I recommend checking out the code,
|
|
||||||
setting up a virtual environment, and installing this package in editable
|
|
||||||
mode. You can confirm your environment works by running the tests. And please
|
|
||||||
do join the [mailing list](https://groups.google.com/forum/#!forum/sucks-users)
|
|
||||||
to discuss your plans.
|
|
||||||
|
|
||||||
For more information see [the development documentation](developing.md).
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## See also
|
|
||||||
|
|
||||||
There are now similar libraries in [Javascript](https://github.com/joostth/sucks.js)
|
|
||||||
and [Go](https://github.com/skburgart/go-vacbot).
|
|
||||||
|
|
||||||
## Thanks
|
|
||||||
|
|
||||||
My heartfelt thanks to:
|
|
||||||
|
|
||||||
* [xmpppeek](https://www.beneaththewaves.net/Software/XMPPPeek.html),
|
|
||||||
a great library for examining XMPP traffic flows (yes, your vacuum
|
|
||||||
speaks Jabbber!),
|
|
||||||
* [mitmproxy](https://mitmproxy.org/), a fantastic tool for analyzing HTTPS,
|
|
||||||
* [click](http://click.pocoo.org/), a wonderfully complete and thoughtful
|
|
||||||
library for making Python command-line interfaces,
|
|
||||||
* [requests](http://docs.python-requests.org/en/master/), a polished Python
|
|
||||||
library for HTTP requests,
|
|
||||||
* [Decompilers online](http://www.javadecompilers.com/apk), which was
|
|
||||||
very helpful in figuring out what the Android app was up to,
|
|
||||||
* Albert Louw, who was kind enough to post code from [his own
|
|
||||||
experiments](https://community.smartthings.com/t/ecovacs-deebot-n79/93410/33)
|
|
||||||
with his device, and
|
|
||||||
* All the users who have given useful feedback and contributed code!
|
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
"""Support for Ecovacs Deebot vacuums."""
|
||||||
|
import logging
|
||||||
|
import random
|
||||||
|
import string
|
||||||
|
|
||||||
|
##import asyncio ## to do
|
||||||
|
|
||||||
|
|
||||||
|
#just included the modified sucks in component
|
||||||
|
from .sucksbumper import EcoVacsAPI, VacBot
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant.const import (
|
||||||
|
CONF_PASSWORD,
|
||||||
|
CONF_USERNAME,
|
||||||
|
CONF_VERIFY_SSL, # added
|
||||||
|
EVENT_HOMEASSISTANT_STOP,
|
||||||
|
Platform,
|
||||||
|
)
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers import discovery
|
||||||
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
from homeassistant.helpers.typing import ConfigType
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
DOMAIN = "ecovacs"
|
||||||
|
|
||||||
|
CONF_COUNTRY = "country"
|
||||||
|
CONF_CONTINENT = "continent"
|
||||||
|
#bumper config vars
|
||||||
|
CONF_BUMPER = "bumper"
|
||||||
|
CONF_BUMPER_SERVER = "bumper_server"
|
||||||
|
server_address = None
|
||||||
|
|
||||||
|
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.Optional(CONF_BUMPER, default=False): cv.boolean,
|
||||||
|
vol.Optional(CONF_BUMPER_SERVER): cv.string,
|
||||||
|
vol.Optional(CONF_VERIFY_SSL, default=True): cv.boolean, # can probably get rid of this and set verify ssl false if bumper true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
},
|
||||||
|
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: HomeAssistant, config: ConfigType) -> bool:
|
||||||
|
"""Set up the Ecovacs component."""
|
||||||
|
_LOGGER.debug("Creating new Ecovacs component")
|
||||||
|
|
||||||
|
hass.data[ECOVACS_DEVICES] = []
|
||||||
|
# if we're using bumper then define the server address
|
||||||
|
if CONF_BUMPER == True:
|
||||||
|
server_address = (config[DOMAIN].get(CONF_BUMPER_SERVER), 5223)
|
||||||
|
# if not make sure it's null
|
||||||
|
else:
|
||||||
|
server_address = None
|
||||||
|
|
||||||
|
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),
|
||||||
|
config[DOMAIN].get(CONF_VERIFY_SSL), # add to class call
|
||||||
|
)
|
||||||
|
|
||||||
|
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.get("did"),
|
||||||
|
device.get("nick"),
|
||||||
|
)
|
||||||
|
vacbot = VacBot(
|
||||||
|
ecovacs_api.uid,
|
||||||
|
ecovacs_api.REALM,
|
||||||
|
ecovacs_api.resource,
|
||||||
|
ecovacs_api.user_access_token,
|
||||||
|
device,
|
||||||
|
config[DOMAIN].get(CONF_CONTINENT).lower(),
|
||||||
|
server_address, # include server address in class, if it's null shoul be no effect
|
||||||
|
config[DOMAIN].get(CONF_VERIFY_SSL), # verify ssl or not
|
||||||
|
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.get("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, Platform.VACUUM, DOMAIN, {}, config)
|
||||||
|
|
||||||
|
return True
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"domain": "ecovacs",
|
||||||
|
"name": "Ecovacs Bumper",
|
||||||
|
"version": "1.3.0",
|
||||||
|
"documentation": "https://www.home-assistant.io/integrations/ecovacs",
|
||||||
|
"requirements": ["sleekxmppfs==1.4.1", "click>=6", "requests>=2.18", "pycryptodome>=3.4", "pycountry-convert>=0.5", "paho-mqtt>=1.4", "stringcase>=1.2"],
|
||||||
|
"codeowners": ["@OverloadUT", "@mib1185"],
|
||||||
|
"iot_class": "local_polling",
|
||||||
|
"loggers": ["sleekxmppfs", "sucksbumper"]
|
||||||
|
}
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
# Describes the format for available vacuum services
|
||||||
|
|
||||||
|
turn_on:
|
||||||
|
name: Turn on
|
||||||
|
description: Start a new cleaning task.
|
||||||
|
target:
|
||||||
|
entity:
|
||||||
|
domain: vacuum
|
||||||
|
|
||||||
|
turn_off:
|
||||||
|
name: Turn off
|
||||||
|
description: Stop the current cleaning task and return to home.
|
||||||
|
target:
|
||||||
|
entity:
|
||||||
|
domain: vacuum
|
||||||
|
|
||||||
|
stop:
|
||||||
|
name: Stop
|
||||||
|
description: Stop the current cleaning task.
|
||||||
|
target:
|
||||||
|
entity:
|
||||||
|
domain: vacuum
|
||||||
|
|
||||||
|
locate:
|
||||||
|
name: Locate
|
||||||
|
description: Locate the vacuum cleaner robot.
|
||||||
|
target:
|
||||||
|
entity:
|
||||||
|
domain: vacuum
|
||||||
|
|
||||||
|
start_pause:
|
||||||
|
name: Start/Pause
|
||||||
|
description: Start, pause, or resume the cleaning task.
|
||||||
|
target:
|
||||||
|
entity:
|
||||||
|
domain: vacuum
|
||||||
|
|
||||||
|
start:
|
||||||
|
name: Start
|
||||||
|
description: Start or resume the cleaning task.
|
||||||
|
target:
|
||||||
|
entity:
|
||||||
|
domain: vacuum
|
||||||
|
|
||||||
|
pause:
|
||||||
|
name: Pause
|
||||||
|
description: Pause the cleaning task.
|
||||||
|
target:
|
||||||
|
entity:
|
||||||
|
domain: vacuum
|
||||||
|
|
||||||
|
return_to_base:
|
||||||
|
name: Return to base
|
||||||
|
description: Tell the vacuum cleaner to return to its dock.
|
||||||
|
target:
|
||||||
|
entity:
|
||||||
|
domain: vacuum
|
||||||
|
|
||||||
|
clean_spot:
|
||||||
|
name: Clean spot
|
||||||
|
description: Tell the vacuum cleaner to do a spot clean-up.
|
||||||
|
target:
|
||||||
|
entity:
|
||||||
|
domain: vacuum
|
||||||
|
|
||||||
|
send_command:
|
||||||
|
name: Send command
|
||||||
|
description: Send a raw command to the vacuum cleaner.
|
||||||
|
target:
|
||||||
|
entity:
|
||||||
|
domain: vacuum
|
||||||
|
fields:
|
||||||
|
command:
|
||||||
|
name: Command
|
||||||
|
description: Command to execute.
|
||||||
|
required: true
|
||||||
|
example: "set_dnd_timer"
|
||||||
|
selector:
|
||||||
|
text:
|
||||||
|
params:
|
||||||
|
name: Parameters
|
||||||
|
description: Parameters for the command.
|
||||||
|
example: '{ "key": "value" }'
|
||||||
|
selector:
|
||||||
|
object:
|
||||||
|
|
||||||
|
set_fan_speed:
|
||||||
|
name: Set fan speed
|
||||||
|
description: Set the fan speed of the vacuum cleaner.
|
||||||
|
target:
|
||||||
|
entity:
|
||||||
|
domain: vacuum
|
||||||
|
fields:
|
||||||
|
fan_speed:
|
||||||
|
name: Fan speed
|
||||||
|
description:
|
||||||
|
Platform dependent vacuum cleaner fan speed, with speed steps, like
|
||||||
|
'medium' or by percentage, between 0 and 100.
|
||||||
|
required: true
|
||||||
|
example: "low"
|
||||||
|
selector:
|
||||||
|
text:
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,186 @@
|
|||||||
|
"""Support for Ecovacs Ecovacs Vacuums."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
#sucks
|
||||||
|
from . import sucksbumper
|
||||||
|
|
||||||
|
from homeassistant.components.vacuum import VacuumEntity, VacuumEntityFeature
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
from homeassistant.helpers.icon import icon_for_battery_level
|
||||||
|
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||||
|
|
||||||
|
from . import ECOVACS_DEVICES
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
ATTR_ERROR = "error"
|
||||||
|
ATTR_COMPONENT_PREFIX = "component_"
|
||||||
|
|
||||||
|
|
||||||
|
def setup_platform(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
config: ConfigType,
|
||||||
|
add_entities: AddEntitiesCallback,
|
||||||
|
discovery_info: DiscoveryInfoType | None = None,
|
||||||
|
) -> None:
|
||||||
|
"""Set up the Ecovacs vacuums."""
|
||||||
|
vacuums = []
|
||||||
|
for device in hass.data[ECOVACS_DEVICES]:
|
||||||
|
vacuums.append(EcovacsVacuum(device))
|
||||||
|
_LOGGER.debug("Adding Ecovacs Vacuums to Home Assistant: %s", vacuums)
|
||||||
|
add_entities(vacuums, True)
|
||||||
|
|
||||||
|
|
||||||
|
class EcovacsVacuum(VacuumEntity):
|
||||||
|
"""Ecovacs Vacuums such as Deebot."""
|
||||||
|
|
||||||
|
_attr_fan_speed_list = [sucksbumper.FAN_SPEED_NORMAL, sucksbumper.FAN_SPEED_HIGH]
|
||||||
|
_attr_should_poll = False
|
||||||
|
_attr_supported_features = (
|
||||||
|
VacuumEntityFeature.BATTERY
|
||||||
|
| VacuumEntityFeature.RETURN_HOME
|
||||||
|
| VacuumEntityFeature.CLEAN_SPOT
|
||||||
|
| VacuumEntityFeature.STOP
|
||||||
|
| VacuumEntityFeature.TURN_OFF
|
||||||
|
| VacuumEntityFeature.TURN_ON
|
||||||
|
| VacuumEntityFeature.LOCATE
|
||||||
|
| VacuumEntityFeature.STATUS
|
||||||
|
| VacuumEntityFeature.SEND_COMMAND
|
||||||
|
| VacuumEntityFeature.FAN_SPEED
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(self, device: sucksbumper.VacBot) -> None:
|
||||||
|
"""Initialize the Ecovacs Vacuum."""
|
||||||
|
self.device = device
|
||||||
|
self.device.connect_and_wait_until_ready()
|
||||||
|
if self.device.vacuum.get("nick") is not None:
|
||||||
|
self._attr_name = str(self.device.vacuum["nick"])
|
||||||
|
else:
|
||||||
|
# In case there is no nickname defined, use the device id
|
||||||
|
self._attr_name = str(format(self.device.vacuum["did"]))
|
||||||
|
|
||||||
|
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 unique_id(self) -> str:
|
||||||
|
"""Return an unique ID."""
|
||||||
|
return self.device.vacuum.get("did")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_on(self) -> bool:
|
||||||
|
"""Return true if vacuum is currently cleaning."""
|
||||||
|
return self.device.is_cleaning
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_charging(self) -> bool:
|
||||||
|
"""Return true if vacuum is currently charging."""
|
||||||
|
return self.device.is_charging
|
||||||
|
|
||||||
|
@property
|
||||||
|
def status(self) -> str | None:
|
||||||
|
"""Return the status of the vacuum cleaner."""
|
||||||
|
return self.device.vacuum_status
|
||||||
|
|
||||||
|
def return_to_base(self, **kwargs: Any) -> None:
|
||||||
|
"""Set the vacuum cleaner to return to the dock."""
|
||||||
|
|
||||||
|
self.device.run(sucksbumper.Charge())
|
||||||
|
|
||||||
|
@property
|
||||||
|
def battery_icon(self) -> str:
|
||||||
|
"""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) -> int | None:
|
||||||
|
"""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) -> str | None:
|
||||||
|
"""Return the fan speed of the vacuum cleaner."""
|
||||||
|
return self.device.fan_speed
|
||||||
|
|
||||||
|
def turn_on(self, **kwargs: Any) -> None:
|
||||||
|
"""Turn the vacuum on and start cleaning."""
|
||||||
|
|
||||||
|
self.device.run(sucksbumper.Clean())
|
||||||
|
|
||||||
|
def turn_off(self, **kwargs: Any) -> None:
|
||||||
|
"""Turn the vacuum off stopping the cleaning and returning home."""
|
||||||
|
self.return_to_base()
|
||||||
|
|
||||||
|
def stop(self, **kwargs: Any) -> None:
|
||||||
|
"""Stop the vacuum cleaner."""
|
||||||
|
|
||||||
|
self.device.run(sucksbumper.Stop())
|
||||||
|
|
||||||
|
def clean_spot(self, **kwargs: Any) -> None:
|
||||||
|
"""Perform a spot clean-up."""
|
||||||
|
|
||||||
|
self.device.run(sucksbumper.Spot())
|
||||||
|
|
||||||
|
def locate(self, **kwargs: Any) -> None:
|
||||||
|
"""Locate the vacuum cleaner."""
|
||||||
|
|
||||||
|
self.device.run(sucksbumper.PlaySound())
|
||||||
|
|
||||||
|
def set_fan_speed(self, fan_speed: str, **kwargs: Any) -> None:
|
||||||
|
"""Set fan speed."""
|
||||||
|
if self.is_on:
|
||||||
|
|
||||||
|
self.device.run(sucksbumper.Clean(mode=self.device.clean_status, speed=fan_speed))
|
||||||
|
|
||||||
|
def send_command(
|
||||||
|
self,
|
||||||
|
command: str,
|
||||||
|
params: dict[str, Any] | list[Any] | None = None,
|
||||||
|
**kwargs: Any,
|
||||||
|
) -> None:
|
||||||
|
"""Send a command to a vacuum cleaner."""
|
||||||
|
self.device.run(sucksbumper.VacBotCommand(command, params))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def extra_state_attributes(self) -> dict[str, Any]:
|
||||||
|
"""Return the device-specific state attributes of this vacuum."""
|
||||||
|
data: dict[str, Any] = {}
|
||||||
|
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
|
||||||
Reference in New Issue
Block a user