FIXED: Ecovacs Deebot 2019 & OZMO Series - Working Library

Hi,
have you solved the problem for displaying the “stanze da pulire”? I still see the message “stanze da pulire: unknow”

@SmartM-ui
No not yet…

After a few hours it also works on the app!
Thanks!
I see all the fields except the status…

Okay, I’ve solved some of my problems.
To adjust the fan speed and the water level, the 2 sensors must be created.

sensor:
  - platform: template
    sensors:
      deebot_fan_speed:
        friendly_name: "fan speed"
        value_template: "{{ state_attr('vacuum.aspirapolvere', 'fan_speed') }}"
      deebot_water_level:
        friendly_name: "water level"
        value_template: "{{ state_attr('vacuum.aspirapolvere', 'water_level') }}"

I have an Ozmo 950 and to see the status of the vacuum in the “xiaomi_card” you have to replace it in the “xiaomi-vacuum-card.js” file posted by @StefanoGiu, vacuum_status with robot_status.

For cleaning the rooms I solved it like this:

  1. I created an input_boolean for each room.
    Example: input_boolean_deebot_cucina, input_boolean_deebot_sala, etc.
  2. I replaced this part of card:
          - color: black
            entity: null
            icon: null
            name: >                 
                [[[
                    var stanze = states['input_text.valuestanze'].state;
                    stanze = stanze.substring(0,stanze.length - 1);
                    return "Stanze da pulire: <br>" + stanze;
                ]]]
            styles:
              card:
                - font-size: 13px
                - background-color: black
                - color: white
              name:
                - width: 100%
                - align: 'left'
                - justify-self: start
                - padding-left: 10px
                - word-wrap: anywhere
            type: 'custom:button-card'

With this:

          - color: black
            entity: null
            icon: null
            name: Stanze da pulire
            styles:
              card:
                - font-size: 16px
                - background-color: black
                - color: white
              name:
                - justify-self: start
                - padding-left: 16px
            type: 'custom:button-card'
  1. I used this script:
vacuum_clean_zone:
  alias: Pulisci zona
  sequence:
  - service: vacuum.send_command
    data_template:
      command: spot_area 
      entity_id: vacuum.aspirapolvere 
      params:
        rooms: >-
              {%- set x = "" %}
              {% if is_state('input_boolean.deebot_cucina', 'on') %}
                {% set x = x + "6," %}
              {% endif %}
              {% if is_state('input_boolean.deebot_sala', 'on') %}
                {% set x = x + "5," %}
              {% endif %}
              {% if is_state('input_boolean.deebot_camera', 'on') %}
                {% set x = x + "3," %}
              {% endif %}
              {% if is_state('input_boolean.deebot_bagno','on') %}
                {% set x = x + "4," %}
              {% endif %}  
              {% if is_state('input_boolean.deebot_lavanderia','on') %}
                {% set x = x + "7," %}
              {% endif %}
              {% if is_state('input_boolean.deebot_corridoio', 'on') %}
                {% set x = x + "1," %}
              {% endif %}  
              {% if is_state('input_boolean.deebot_cameretta', 'on') %}
                {% set x = x + "8," %}
              {% endif %}
              {% if is_state('input_boolean.deebot_studio', 'on') %}
                {% set x = x + "0," %}
              {% endif %}
                {{x[:-1]}}
        cleanings: >-
              {%- if is_state('input_boolean.deebot_times', 'on') -%}2{%- else
              -%}1{%- endif -%}
  - service: input_boolean.turn_off
    data:
       entity_id: input_boolean.deebot_cucina
  - service: input_boolean.turn_off
    data:
       entity_id: input_boolean.deebot_sala 
  - service: input_boolean.turn_off
    data:
       entity_id: input_boolean.deebot_camera
  - service: input_boolean.turn_off
    data:
       entity_id: input_boolean.deebot_bagno
  - service: input_boolean.turn_off
    data:
       entity_id: input_boolean.deebot_lavanderia
  - service: input_boolean.turn_off
    data:
       entity_id: input_boolean.deebot_corridoio
  - service: input_boolean.turn_off
    data:
       entity_id: input_boolean.deebot_cameretta
  - service: input_boolean.turn_off
    data:
       entity_id: input_boolean.deebot_studio
  - service: input_boolean.turn_off
    data:
       entity_id: input_boolean.deebot_times   
1 Like

Hello,
I have an ecovacs deebot 950, not working at all with H.A (not able to log in).
Can you tell me if an update of the integration will be available in the future ?
Best regards.
Thierry

Hi @vormsty if got my 950 working last week. I can give you my config tomorrow.
Cheers

oh nice, for schur I will be very interested !

Very nice, keep me please in touch !

best regards

Thierry

This is how it looks like in my interface…
3AFB34AC-9DF5-41C5-8312-A91896131B8D_4_5005_c

What I die after quite a while of tinkering:

This is my working config even thou I am not in the US (am in Norway)

vacuum:

  • platform: deebot
    username: !secret deebot_username
    password: !secret deebot_password
    country: us
    continent: na
    deviceid: !secret deebot_deviceid
    livemappath: ‘www/live_map.png’

In order to get it working with the xiaomi-vacuum-card, you’ll need to edit the xiaomi-vacuum-card.js file.

  • open the file with your preferred editor its in
    www/community/lovelace-xiaomi-vacuum-card/xiaomi-vacuum-card.js
  • delete the content and replace with this version
((LitElement) => {
    const html = LitElement.prototype.html;
    const css = LitElement.prototype.css;

    class XiaomiVacuumCard extends LitElement {

        static get properties() {
            return {
                _hass: {},
                _config: {},
                stateObj: {},
                state: {},
                style: {}
            }
        }

        static get styles() {
            return css`
        .background {
          background-repeat: no-repeat;
          background-position: center center;
          background-size: cover;
        }
        .title {
          font-size: 20px;
          padding: 16px 16px 0;
          text-align: center;
          white-space: nowrap;
          text-overflow: ellipsis;
          overflow: hidden;
        }
        .content {
          cursor: pointer;
        }
        .flex {
          display: flex;
          align-items: center;
          justify-content: space-evenly;
        }
        .button {
          cursor: pointer;
          padding: 16px;
        }
        .grid {
          display: grid;
          grid-template-columns: repeat(2, auto);
        }
        .grid-content {
          display: grid;
          align-content: space-between;
          grid-row-gap: 6px;
        }
        .grid-left {
          text-align: left;
          font-size: 110%;
          padding-left: 10px;
          border-left: 2px solid var(--primary-color);
        }
        .grid-right {
          text-align: right;
          padding-right: 10px;
          border-right: 2px solid var(--primary-color);
        }`;
        }

        render() {
            return html`
            <ha-card .hass="${this._hass}" .config="${this._config}" class="background" style="${this.style.background}">
              ${this.state.name ?
                html`<div class="title" style="${this.style.text}" @click="${() => this.fireEvent('hass-more-info')}">${this.state.name}</div>`
                : null}
              ${this.state.showLabels ? html`
              <div class="content grid" style="${this.style.content + this.style.text}" @click="${() => this.fireEvent('hass-more-info')}">
                <div class="grid-content grid-left">
                  <div>${this.getValue('vacuum_status')}</div>
                  <div>${this.getValue('battery', ' %')}</div>
                  <div>${this.getValue('mode')}</div>
                </div>
                ${this.state.showDetails ? html`
                <div class="grid-content grid-right" >
                  <div>${this.getValue('main_brush', ' %')}</div>
                  <div>${this.getValue('side_brush', ' %')}</div>
                  <div>${this.getValue('filter', ' %')}</div>
                  <div>${this.getValue('water_level')}</div>
                </div>` : null}
              </div>` : null}
              ${this.state.showButtons ? html`
              <div class="flex" style="${this.style.text}">
                ${Object.keys(this.state.buttons).map(this.renderButton.bind(this))}
              </div>` : null}
            </ha-card>`;
        }

        renderButton(key) {
            return this.state.buttons[key]
                ? html`<div class="button" @tap="${() => this.callService(key)}"><ha-icon icon="${this.state.icons[key]}"></ha-icon></div>`
                : null;
        }

        getValue(field, unit = '') {
            const value = (this.stateObj && this.state.attributes[field] in this.stateObj.attributes)
                ? this.stateObj.attributes[this.state.attributes[field]] + unit
                : (this._hass ? this._hass.localize('state.default.unavailable') : 'Unavailable');
            return `${this.state.labels[field]}: ${value}`;
        };

        computeValue(field) {
            if (this.state.attributes[field] === undefined || this.state.attributes[field] === false) {
                return null;
            } else if (this.stateObj && this.state.attributes[field] in this.stateObj.attributes) {
                const computed = this.state.computeValue(this.stateObj.attributes[this.state.attributes[field]]);
                const unit = typeof computed === 'number' ? ` ${this.state.labels.hours}` : '';
                return `${this.state.labels[field]}: ${computed}${unit}`;
            } else {
                return `${this.state.labels[field]}: - `;
            }
        };

        callService(service) {
            this._hass.callService('vacuum', this.state.service[service], {entity_id: this.stateObj.entity_id});
        }

        fireEvent(type, options = {}) {
            const event = new Event(type, {
                bubbles: options.bubbles || true,
                cancelable: options.cancelable || true,
                composed: options.composed || true,
            });
            event.detail = {entityId: this.stateObj.entity_id};
            this.dispatchEvent(event);
        }

        getCardSize() {
            if (this.state.name && this.state.showButtons) return 5;
            if (this.state.name || this.state.showButtons) return 4;
            return 3;
        }

        setConfig(config) {
            const labels = {
                vacuum_status: 'Status',
                battery: 'Battery',
                mode: 'Mode',
                main_brush: 'Main Brush',
                side_brush: 'Main Brush',
                filter: 'Filter',
                water_level: 'Water Level',
                hours: 'h',
            };

            const attributes = {
                vacuum_status: 'robot_status',
                battery: 'battery_level',
                mode: 'fan_speed',
                main_brush: 'component_brush',
                side_brush: 'component_sideBrush',
                filter: 'component_heap',
                water_level: 'water_level',
            };

            const services = {
                start: 'start',
                pause: 'pause',
                stop: 'stop',
                locate: 'locate',
                return: 'return_to_base',
                spot: 'clean_spot',
            };

            const buttons = {
                start: true,
                pause: true,
                stop: true,
                spot: false,
                locate: true,
                return: true,
            };

            const icons = {
                start: 'mdi:play',
                pause: 'mdi:pause',
                stop: 'mdi:stop',
                locate: 'mdi:map-marker',
                return: 'mdi:home-map-marker',
                spot: 'mdi:broom',
            };

            const vendors = {
                xiaomi: {
                    details: true,
                },
                valetudo: {
                    details: true,
                    attributes: {
                        vacuum_status: 'vacuum_status',
                        main_brush: 'mainBrush',
                        side_brush: 'sideBrush',
                        filter: 'filter',
                        sensor: 'water_level',
                    },
                },
                roomba: {
                    details: true,
                    attributes: {
                        main_brush: 'bin_present',
                        side_brush: 'bin_full',
                        filter: false,
                        sensor: false,
                    },
                    labels: {
                        main_brush: 'Bin Present',
                        side_brush: 'Bin Full',
                    },
                    computeValue: v => (v === true ? 'Yes' : (v === false ? 'No' : '-')),
                },
                robovac: {
                    details: false,
                    buttons: {
                        stop: false,
                        spot: true,
                    },
                },
                ecovacs: {
                    image: '/local/img/vacuum_ecovacs.png',
                    details: false,
                    buttons: {
                        stop: false,
                        spot: true,
                    },
                    service: {
                        start: 'turn_on',
                        pause: 'stop',
                        stop: 'turn_off',
                    },
                },
                deebot: {
                    image: '/local/img/vacuum_ecovacs.png',
                    details: true,
                    service: {
                        start: 'turn_on',
                        pause: 'stop',
                        stop: 'turn_off',
                    },
                    attributes: {
                        main_brush: 'component_brush',
                        side_brush: 'component_sideBrush',
                        filter: 'component_heap',
                        water_level: 'water_level',
                    },
                }
            };

            if (!config.entity) throw new Error('Please define an entity.');
            if (config.entity.split('.')[0] !== 'vacuum') throw new Error('Please define a vacuum entity.');
            if (config.vendor && !config.vendor in vendors) throw new Error('Please define a valid vendor.');

            const vendor = vendors[config.vendor] || vendors.xiaomi;

            this.state = {
                showDetails: vendor.details,
                showButtons: config.buttons !== false,
                showLabels: config.labels !== false,
                showName: config.name !== false,

                service: Object.assign({}, services, vendor.service),
                buttons: Object.assign({}, buttons, vendor.buttons, config.buttons),
                attributes: Object.assign({}, attributes, vendor.attributes, config.attributes),
                labels: Object.assign({}, labels, vendor.labels, config.labels),
                icons: Object.assign({}, icons, config.icons),
                computeValue: vendor.computeValue || (val => val),
            };

            this.style = {
                text: `color: ${config.image !== false ? 'white; text-shadow: 0 0 10px black;' : 'var(--primary-text-color);'}`,
                content: `padding: ${config.showButtons ? '16px 16px 4px' : '16px'};`,
                background: config.image !== false ? `background-image: url('${config.image || vendor.image || '/local/img/vacuum.png'}')` : ''
            };

            this._config = config;
        }

        set hass(hass) {
            this._hass = hass;

            if (hass && this._config) {
                this.stateObj = this._config.entity in hass.states ? hass.states[this._config.entity] : null;

                if (this.stateObj && this.state.showName) {
                    this.state.name = this._config.name || this.stateObj.attributes.friendly_name;
                }
            }
        }
    }

    customElements.define('xiaomi-vacuum-card', XiaomiVacuumCard);
})(window.LitElement || Object.getPrototypeOf(customElements.get("hui-view")));

After that you should be able to integrate your Deebot 950 into your dashboard.
If you have problems ill be here :wink:

1 Like

Hello,
Can you tell me how to know the device id ?

Regards
Thierry

Read the read me to the 950 component.
https://github.com/And3rsL/Deebot-for-hassio/blob/4823caa80362b80775bb67e350d0af40b2b541c8/README.md
It’s the serial number in the deebot app :wink:

Hello,
Yes I see the id…
unfortunaltly, HA is not able to log in…
Here is the log:

Uncaught exception

13:02:00 – util/thread.py (ERROR)

Uncaught exception

13:01:58 – util/thread.py (ERROR)

MapID not found – did you finish your first auto cleaning?

13:01:58 – util/thread.py (WARNING)

Entity id already exists - ignoring: person.vorms. Platform person does not generate unique IDs

13:01:55 – Personne (ERROR)

Platform squeezebox not ready yet. Retrying in 30 seconds.

13:01:55 – Lecteur multimédia (WARNING)

Failed communicating with LMS: <class ‘aiohttp.client_exceptions.ClientConnectorError’>

13:01:54 – main.py (ERROR)

You are using a custom integration for deebot which has not been tested by Home Assistant. This component might cause stability problems, be sure to disable it if you experience issues with Home Assistant.

13:01:53 – loader.py (WARNING)

Many thanks for your help.

Best regards

Sorry I can’t help you with this log either :frowning:
Maybe look here if you can find someone with the same problem. Or open a new issue.
https://github.com/And3rsL/Deebot-for-hassio/issues?q=

Just a short feedback, the integration from And3rsL (installed via HACS) works - as of now - fine with my Ozmo T8 AIVI here :slight_smile:
Using it with the Vacuum Card i installed from HACS

Hi, I have a T8 AIVI as well, can you tell me the procedure you followed to integrate it please? Can we also get the camera back on HA?

Thank you in advance for your help.

Hi @niconath,

i just installed HACS, in HACS i used the big “+”-Button to search for “deebot” and added the integrations repository.
After that i just added this to my configuration:

vacuum:
  - platform: deebot
    username: !secret ecovacs_user
    password: !secret ecovacs_pass
    country: DE
    continent: EU
    deviceid: !secret ecovacs_deviceid
    # Optional
    live_map: True                    # Enable Live Map.. may cause issues on low power hardware |       Default: True
    show_color_rooms: True           # Enable draw room colors as in the app | Default: False
    livemappath: 'www/live_map.png'   # Path where to save live_map

camera:
# Saugroboter
- platform: generic
    name: Vacuum Map
    still_image_url: "https://192.168.178.101:8123/local/live_map.png" #Example configuration for   livemappath: 'www/live_map.png'
    verify_ssl: false

The camera Live-Image is as far as i know not supported. As of now i think it is only available when using the bot in patrol mode, so not while its cleaning as it uses it for the AIVI functions exclusive

Okay, great, thanks to you. Do you know if it’s possible to retrieve the map and select the washing areas on HA as on the app?

Yes, you get the map as a pseudo-camera (as shown in my code-example).
For the areas you might need to consult the documentation, it seems to be possible to name the areas as recognized by the robot and set custom / area cleanings and start by calling the service.

Read the docs for this :slight_smile: (just scroll down): https://github.com/And3rsL/Deebot-for-hassio

Okay, that’s all right. I made it. Thanks for your help.

TypeError: ‘NoneType’ object is not callable

Hi,
lately I always have this error and the icon marked in yellow does not change color returning to the previous state. Do you also have the same mistake?

Logger: homeassistant.components.script
Source: custom_components/deebot/vacuum.py:222
Integration: Script (documentation, issues)
First occurred: 15:00:32 (1 occurrences)
Last logged: 15:00:32

Pulisci zona: Error executing script. Unexpected error for call_service at pos 1: ‘NoneType’ object is not callable

Traceback (most recent call last): File “/usr/src/homeassistant/homeassistant/helpers/script.py”, line 153, in _async_step self, f"async{cv.determine_script_action(self._action)}_step" File “/usr/src/homeassistant/homeassistant/helpers/script.py”, line 656, in _async_call_service_step *self._prep_call_service_step(), blocking=True, context=self._context File “/usr/src/homeassistant/homeassistant/core.py”, line 1260, in async_call task.result() File “/usr/src/homeassistant/homeassistant/core.py”, line 1295, in _execute_service await handler.func(service_call) File “/usr/src/homeassistant/homeassistant/helpers/entity_component.py”, line 209, in handle_service self._platforms.values(), func, call, required_features File “/usr/src/homeassistant/homeassistant/helpers/service.py”, line 454, in entity_service_call future.result() # pop exception if have File “/usr/src/homeassistant/homeassistant/helpers/entity.py”, line 595, in async_request_call await coro File “/usr/src/homeassistant/homeassistant/helpers/service.py”, line 485, in _handle_entity_call await result File “/config/custom_components/deebot/vacuum.py”, line 222, in async_send_command return await self.hass.async_add_executor_job(self.device.SpotArea(params[‘rooms’], params[‘cleanings’])) File “/usr/local/lib/python3.7/concurrent/futures/thread.py”, line 57, in run result = self.fn(*self.args, **self.kwargs) TypeError: ‘NoneType’ object is not callable

error resolved with update
Thanks @Andrea_Liosi