Skip to content
This repository has been archived by the owner on Mar 8, 2022. It is now read-only.

Config entry and various changes #91

Merged
merged 28 commits into from
Apr 26, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
a4626ff
Config entry and various changes
And3rsL Mar 9, 2021
76f4bcf
Update packages to beta channel
And3rsL Mar 9, 2021
0f72ff1
Fixed Leftover
And3rsL Mar 9, 2021
a5e5c21
Translatons and LiveMap Camera!
And3rsL Mar 9, 2021
7246eab
add version to manifest and workflow to set it
edenhaus Mar 9, 2021
15e358f
subscribe to water events
edenhaus Mar 9, 2021
343a007
Merge pull request #89 from edenhaus/version
And3rsL Mar 10, 2021
4fd1baa
add german translations
edenhaus Mar 10, 2021
1285043
New repo link and readme update
And3rsL Mar 10, 2021
5c288ce
Merge branch 'beta' of https://github.com/And3rsL/Deebot-for-Home-Ass…
And3rsL Mar 10, 2021
4ce2227
remove removed field
edenhaus Mar 10, 2021
9fa0be5
add hassfest validation
edenhaus Mar 10, 2021
87038bb
Unload & warnings fix
And3rsL Mar 10, 2021
c8e3973
disable sensors as default
edenhaus Mar 10, 2021
ccc35e8
subscribe to the correct event for each sensor
edenhaus Mar 10, 2021
9253bc3
fix schedule_update_ha_state
edenhaus Mar 10, 2021
66e4c80
hass object is required for schedule_update_ha_state
edenhaus Mar 10, 2021
ed681e7
fix copy paste error
edenhaus Mar 10, 2021
fe3d150
Merge branch 'beta' into beta-mop
edenhaus Mar 10, 2021
a0d46a9
fix schedule_update_ha_state
edenhaus Mar 10, 2021
0c3098a
Merge pull request #93 from edenhaus/beta-mop
edenhaus Mar 10, 2021
c4b1cb4
add device info
edenhaus Mar 10, 2021
9042a44
remove camera_image
edenhaus Mar 10, 2021
8597c26
Add FWVersion and Model Name
And3rsL Mar 11, 2021
d08f71c
add event unsubscribe for vacuum
edenhaus Mar 11, 2021
03dc3cd
call setScheduleUpdates in separate Task to create the config entry q…
edenhaus Mar 11, 2021
467bf47
Update hacs.json
And3rsL Mar 23, 2021
09cf828
Merge branch 'master' into beta
And3rsL Apr 26, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .github/workflows/hassfest.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
name: Validate with hassfest

on:
push:
pull_request:
schedule:
- cron: "0 0 * * *"

jobs:
validate:
runs-on: "ubuntu-latest"
steps:
- uses: "actions/checkout@v2"
- uses: home-assistant/actions/hassfest@master
61 changes: 61 additions & 0 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# This GitHub action workflow is meant to be copyable to any repo that have the same structure.
# - Your integration exist under custom_components/{INTEGRATION_NAME}/[integration files]
# - You are using GitHub releases to publish new versions
# - You have a INTEGRATION_VERSION constant in custom_components/{INTEGRATION_NAME}/const.py

name: Release Workflow

on:
release:
types: [published]

jobs:
release:
name: Release
runs-on: ubuntu-latest
steps:
- name: 📥 Checkout the repository
uses: actions/checkout@v2

- name: 🔢 Get release version
id: version
uses: home-assistant/actions/helpers/version@master

- name: ℹ️ Get integration information
id: information
run: |
name=$(find custom_components/ -type d -maxdepth 1 | tail -n 1 | cut -d "/" -f2)
echo "::set-output name=name::$name"
- name: 🖊️ Set version number
run: |
sed -i '/INTEGRATION_VERSION = /c\INTEGRATION_VERSION = "${{ steps.version.outputs.version }}"' \
"${{ github.workspace }}/custom_components/${{ steps.information.outputs.name }}/const.py"
jq '.version = "${{ steps.version.outputs.version }}"' \
"${{ github.workspace }}/custom_components/${{ steps.information.outputs.name }}/manifest.json" > tmp \
&& mv -f tmp "${{ github.workspace }}/custom_components/${{ steps.information.outputs.name }}/manifest.json"
- name: 👀 Validate data
run: |
if ! grep -q 'INTEGRATION_VERSION = "${{ steps.version.outputs.version }}"' ${{ github.workspace }}/custom_components/${{ steps.information.outputs.name }}/const.py; then
echo "The version in custom_components/${{ steps.information.outputs.name }}/const.py was not correct"
cat ${{ github.workspace }}/custom_components/${{ steps.information.outputs.name }}/const.py | grep INTEGRATION_VERSION
exit 1
fi
manifestversion=$(jq -r '.version' ${{ github.workspace }}/custom_components/${{ steps.information.outputs.name }}/manifest.json)
if [ "$manifestversion" != "${{ steps.version.outputs.version }}" ]; then
echo "The version in custom_components/${{ steps.information.outputs.name }}/manifest.json was not correct"
echo "$manifestversion"
exit 1
fi
- name: 📦 Create zip file for the integration
run: |
cd "${{ github.workspace }}/custom_components/${{ steps.information.outputs.name }}"
zip ${{ steps.information.outputs.name }}.zip -r ./
- name: 📤 Upload the zip file as a release asset
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ github.event.release.upload_url }}
asset_path: "${{ github.workspace }}/custom_components/${{ steps.information.outputs.name }}/${{ steps.information.outputs.name }}.zip"
asset_name: ${{ steps.information.outputs.name }}.zip
asset_content_type: application/zip
47 changes: 4 additions & 43 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,24 +31,7 @@ With this Home Assistant Custom Component you'll be able to
* and much more...

## Configuration
To add your Ecovacs devices into your Home Assistant installation, add the following to your configuration.yaml file:

```
# required fields
deebot:
username: YOUR_ECOVACS_USERNAME
password: YOUR_ECOVACS_PASSWORD
country: YOUR_TWO_LETTER_COUNTRY_CODE
continent: YOUR_TWO_LETTER_CONTINENT_CODE
deviceid:
- YOUR_ROBOT_ID
- YOUR_ROBOT_ID2
- etc...
# Optional
live_map: True # Enable Live Map.. may cause issues on low power hardware | Default: True
show_color_rooms: False # Enable draw room colors as in the app.. BE Carefull, very experimental. first thing to disable if there is any issue | Default: False
livemappath: 'www/' # Path where to save live_map (Each bot will have XXX_liveMap.png where XXX is the vacbot name)
```
To add your Ecovacs devices into your Home Assistant installation, follow the steps in Home Assistant -> Settings -> Integration -> Add -> Deebot for Home Assistant

### Chinese server Configuration
For chinese server username you require "short id" and password. short id look like "EXXXXXX". DO NOT USE YOUR MOBILE PHONE NUMBER, it wont work.
Expand All @@ -58,11 +41,6 @@ continent: as (or ww)

Since these servers are in china and unless you are close to china, don't expect very fast response.

### DeviceID
You can find your robot id under settings and "About Deebot" or inside the robot (normally under dust bin)

![Preview](images/deviceid.jpg)

### Country and Continent code
#### country:
Your two-letter country code (us, uk, etc).
Expand All @@ -83,6 +61,7 @@ Additional note: There are some issues during the password encoding. Using some

### Sensors
This integration expose a number of sensors
**All sensors are disabled by default.** You can enable only the required ones.

* sensor.ROBOTNAME_last_clean_image (A URL to the last success clean -- the image is on the ecovacs servers)
* sensor.ROBOTNAME_brush (% main brush)
Expand All @@ -95,26 +74,8 @@ This integration expose a number of sensors
* binary_sensor.ROBOTNAME_mop_attached (On/off is mop is attached)

### Live Map:
If is true live_map it will try to generate a live map in the specified folder
you can set a generic camera example:

Add Camera in configuration.yaml

```
camera:
- platform: generic
name: Deebot_live_map
still_image_url: "http://YOURLOCALIP:8123/local/YOUR_ROBOT_NAME_liveMap.png" #Example configuration for livemappath: 'www/'
verify_ssl: false
```

YAML interface:
```
type: picture-entity
entity: vacuum.YOUR_ROBOT_NAME
aspect_ratio: 50%
camera_image: camera.deebot_live_map
```
If is true live_map it will try to generate a live map camera feed
* camera.ROBOTNAME_liveMap

### Suggested yaml component
A suggested custom lovelace card that i use is: vacuum-card by denysdovhan link: https://github.com/denysdovhan/vacuum-card
Expand Down
149 changes: 46 additions & 103 deletions custom_components/deebot/__init__.py
Original file line number Diff line number Diff line change
@@ -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."""

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)
)

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 = []
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
)

# 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'])
# 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)
)

self.vacbots.append(vacbot)

_LOGGER.debug("Hub initialized")
return True

@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"
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
]
)
)

if unload_ok:
hass.data[DOMAIN][entry.entry_id].disconnect()
hass.data[DOMAIN].pop(entry.entry_id)

return unload_ok
40 changes: 34 additions & 6 deletions custom_components/deebot/binary_sensor.py
Original file line number Diff line number Diff line change
@@ -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):
Expand All @@ -33,11 +38,20 @@ def __init__(self, vacbot: VacBot, device_id: str):

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
Expand All @@ -46,3 +60,17 @@ def is_on(self):
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 False

@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())
Loading