Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vacuum cleaner #9

Open
timota opened this issue Jun 9, 2021 · 16 comments
Open

Vacuum cleaner #9

timota opened this issue Jun 9, 2021 · 16 comments
Labels
enhancement Request new features

Comments

@timota
Copy link

timota commented Jun 9, 2021

Is any plans to add support for Vacuum cleaner ?

Thanks

@remb0
Copy link

remb0 commented Jun 16, 2021

I also hope it :)

@tsutsuku
Copy link
Collaborator

@timota @remb0 We will consider it.

@frankosborne
Copy link
Contributor

I got a vacuum yesterday that was run off smart life. I got too keen to get this going so I have produced this. Likely have some code thats not needed but it got my vacuum working. Weirdly enough, HA doesnt seem to be doing anything when you call the start and pause services for Vacuums. I don't think their is anything wrong with my implementation as I cant even see the function get called. @tsutsuku. I hope this helps even a little to getting this into this repo :) I will try and clean this up a bit when I have time but it works. :)

#!/usr/bin/env python3
"""Support for Tuya vacuum."""
from __future__ import annotations

import logging
from typing import Any

from tuya_iot import TuyaDevice, TuyaDeviceManager

from homeassistant.components.vacuum import (
    DOMAIN as DEVICE_DOMAIN, 
    SUPPORT_TURN_ON, SUPPORT_TURN_OFF, 
    SUPPORT_STATE, 
    SUPPORT_STATUS, 
    SUPPORT_BATTERY,
    SUPPORT_START,
    SUPPORT_PAUSE,
    SUPPORT_RETURN_HOME,
    SUPPORT_STOP,
    STATE_CLEANING,
    STATE_DOCKED,
    STATE_PAUSED,
    STATE_IDLE,
    STATE_RETURNING,
    STATE_ERROR,
    VacuumEntity
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.dispatcher import async_dispatcher_connect

from .base import TuyaHaDevice
from .const import (
    DOMAIN,
    TUYA_DEVICE_MANAGER,
    TUYA_DISCOVERY_NEW,
    TUYA_HA_DEVICES,
    TUYA_HA_TUYA_MAP,
)

_LOGGER = logging.getLogger(__name__)

TUYA_SUPPORT_TYPE = {
    "sd",  # Robot Vaccuum
}

# Vacuum(sd),
# https://developer.tuya.com/docs/iot/open-api/standard-function/electrician-category/categorykgczpc?categoryId=486118
DPCODE_MODE = "mode"
DPCODE_POWER = "power_go"
DPCODE_STATUS = "status"

TUYA_VMODE_TO_HA = {
    "hot": STATE_CLEANING,
    "cold": "cool",
}



async def async_setup_entry(
    hass: HomeAssistant, entry: ConfigEntry, async_add_entities
):
    """Set up tuya vacuum dynamically through tuya discovery."""
    print("switch init")

    hass.data[DOMAIN][TUYA_HA_TUYA_MAP].update({DEVICE_DOMAIN: TUYA_SUPPORT_TYPE})

    async def async_discover_device(dev_ids):
        """Discover and add a discovered tuya sensor."""
        print("switch add->", dev_ids)
        if not dev_ids:
            return
        entities = await hass.async_add_executor_job(_setup_entities, hass, dev_ids)
        hass.data[DOMAIN][TUYA_HA_DEVICES].extend(entities)
        async_add_entities(entities)

    async_dispatcher_connect(
        hass, TUYA_DISCOVERY_NEW.format(DEVICE_DOMAIN), async_discover_device
    )

    device_manager = hass.data[DOMAIN][TUYA_DEVICE_MANAGER]
    device_ids = []
    for (device_id, device) in device_manager.deviceMap.items():
        if device.category in TUYA_SUPPORT_TYPE:
            device_ids.append(device_id)
    await async_discover_device(device_ids)


def _setup_entities(hass, device_ids: list):
    """Set up Tuya Switch device."""
    device_manager = hass.data[DOMAIN][TUYA_DEVICE_MANAGER]
    entities = []
    for device_id in device_ids:
        device = device_manager.deviceMap[device_id]
        if device is None:
            continue

        entities.append(TuyaHaVacuum(device, device_manager))

    return entities



class TuyaHaVacuum(TuyaHaDevice, VacuumEntity):
    """Tuya Vacuum Device."""

    HASTATE = STATE_IDLE

    @property
    def name(self) -> str | None:
        """Return Tuya device name."""
        return self.tuya_device.name

    @property
    def status(self) -> str | None:
        """Return Tuya device state."""
        status = self.tuya_device.status.get(DPCODE_STATUS)
        if status == "standby":
            HASTATE = STATE_IDLE
            return "Standby"
        if status == "goto_charge":
            HASTATE = STATE_RETURNING
            return "Returning to Dock"
        if status == "charging":
            HASTATE = STATE_DOCKED
            return "Docked - Charging"
        if status == "charge_done":
            HASTATE = STATE_DOCKED
            return "Docked - Charged"
        HASTATE = STATE_CLEANING
        return "Cleaning"


    @property
    def battery_level(self) -> int | None:
        """Return Tuya device state."""
        return self.tuya_device.status.get("electricity_left")

    @property
    def state(self):
        return self.HASTATE
    
    @property
    def supported_features(self):
        """Flag supported features."""
        supports = 0
        supports = supports | SUPPORT_TURN_ON
        supports = supports | SUPPORT_TURN_OFF
        supports = supports | SUPPORT_PAUSE
        supports = supports | SUPPORT_RETURN_HOME
        supports = supports | SUPPORT_STATE
        supports = supports | SUPPORT_STATUS
        supports = supports | SUPPORT_STOP
        supports = supports | SUPPORT_BATTERY
        supports = supports | SUPPORT_START
        return supports



##Functions:##
    def turn_on(self, **kwargs: Any) -> None:
        """Turn the device on."""
        _LOGGER.debug("Turning on %s", self.name)

        self.tuya_device_manager.sendCommands(
            self.tuya_device.id, [{"code": DPCODE_POWER, "value": True}]
        )
    
    def start(self, **kwargs: Any) -> None:
        """Turn the device on."""
        _LOGGER.debug("Starting %s", self.name)

        self.tuya_device_manager.sendCommands(
            self.tuya_device.id, [{"code": DPCODE_POWER, "value": True}]
        )

## Turn off/pause/stop all do the same thing

    def turn_off(self, **kwargs: Any) -> None:
        """Turn the device off."""
        _LOGGER.debug("Turning off %s", self.name)
        self.tuya_device_manager.sendCommands(
            self.tuya_device.id, [{"code": DPCODE_POWER, "value": False}]
        )

    def stop(self, **kwargs: Any) -> None:
        """Turn the device off."""
        _LOGGER.debug("Stopping %s", self.name)
        self.tuya_device_manager.sendCommands(
            self.tuya_device.id, [{"code": DPCODE_POWER, "value": False}]
        )
    
    def pause(self, **kwargs: Any) -> None:
        """Turn the device off."""
        _LOGGER.debug("Pausing %s", self.name)
        self.tuya_device_manager.sendCommands(
            self.tuya_device.id, [{"code": DPCODE_POWER, "value": False}]
        )
    
    def start_pause(self, **kwargs: Any) -> None:
        """Start/Pause the device"""
        _LOGGER.debug("Start/Pausing %s", self.name)
        status = False
        status = self.tuya_device.status.get(DPCODE_POWER)
        self.tuya_device_manager.sendCommands(
            self.tuya_device.id, [{"code": DPCODE_POWER, "value": not status}]
        )

    def return_to_base(self, **kwargs: Any) -> None:
        """Return device to Dock"""
        _LOGGER.debug("Return to base device %s", self.name)
        self.tuya_device_manager.sendCommands(
            self.tuya_device.id, [{"code": DPCODE_MODE, "value": "chargego"}]
        )

@tsutsuku tsutsuku added the enhancement Request new features label Jun 24, 2021
@tsutsuku
Copy link
Collaborator

@frankosborne Thank you for your code.
The vacuum cleaner will be supported in v1.3 which will be released this week.

@tsutsuku
Copy link
Collaborator

@frankosborne vacuum cleaner is supported in v1.3, which released today.

@maweki
Copy link

maweki commented Jun 30, 2021

The Readme is missing the information about these device types

@freitas666
Copy link

I got a vacuum yesterday that was run off smart life. I got too keen to get this going so I have produced this. Likely have some code thats not needed but it got my vacuum working. Weirdly enough, HA doesnt seem to be doing anything when you call the start and pause services for Vacuums. I don't think their is anything wrong with my implementation as I cant even see the function get called. @tsutsuku. I hope this helps even a little to getting this into this repo :) I will try and clean this up a bit when I have time but it works. :)

#!/usr/bin/env python3
"""Support for Tuya vacuum."""
from __future__ import annotations

import logging
from typing import Any

from tuya_iot import TuyaDevice, TuyaDeviceManager

from homeassistant.components.vacuum import (
    DOMAIN as DEVICE_DOMAIN, 
    SUPPORT_TURN_ON, SUPPORT_TURN_OFF, 
    SUPPORT_STATE, 
    SUPPORT_STATUS, 
    SUPPORT_BATTERY,
    SUPPORT_START,
    SUPPORT_PAUSE,
    SUPPORT_RETURN_HOME,
    SUPPORT_STOP,
    STATE_CLEANING,
    STATE_DOCKED,
    STATE_PAUSED,
    STATE_IDLE,
    STATE_RETURNING,
    STATE_ERROR,
    VacuumEntity
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.dispatcher import async_dispatcher_connect

from .base import TuyaHaDevice
from .const import (
    DOMAIN,
    TUYA_DEVICE_MANAGER,
    TUYA_DISCOVERY_NEW,
    TUYA_HA_DEVICES,
    TUYA_HA_TUYA_MAP,
)

_LOGGER = logging.getLogger(__name__)

TUYA_SUPPORT_TYPE = {
    "sd",  # Robot Vaccuum
}

# Vacuum(sd),
# https://developer.tuya.com/docs/iot/open-api/standard-function/electrician-category/categorykgczpc?categoryId=486118
DPCODE_MODE = "mode"
DPCODE_POWER = "power_go"
DPCODE_STATUS = "status"

TUYA_VMODE_TO_HA = {
    "hot": STATE_CLEANING,
    "cold": "cool",
}



async def async_setup_entry(
    hass: HomeAssistant, entry: ConfigEntry, async_add_entities
):
    """Set up tuya vacuum dynamically through tuya discovery."""
    print("switch init")

    hass.data[DOMAIN][TUYA_HA_TUYA_MAP].update({DEVICE_DOMAIN: TUYA_SUPPORT_TYPE})

    async def async_discover_device(dev_ids):
        """Discover and add a discovered tuya sensor."""
        print("switch add->", dev_ids)
        if not dev_ids:
            return
        entities = await hass.async_add_executor_job(_setup_entities, hass, dev_ids)
        hass.data[DOMAIN][TUYA_HA_DEVICES].extend(entities)
        async_add_entities(entities)

    async_dispatcher_connect(
        hass, TUYA_DISCOVERY_NEW.format(DEVICE_DOMAIN), async_discover_device
    )

    device_manager = hass.data[DOMAIN][TUYA_DEVICE_MANAGER]
    device_ids = []
    for (device_id, device) in device_manager.deviceMap.items():
        if device.category in TUYA_SUPPORT_TYPE:
            device_ids.append(device_id)
    await async_discover_device(device_ids)


def _setup_entities(hass, device_ids: list):
    """Set up Tuya Switch device."""
    device_manager = hass.data[DOMAIN][TUYA_DEVICE_MANAGER]
    entities = []
    for device_id in device_ids:
        device = device_manager.deviceMap[device_id]
        if device is None:
            continue

        entities.append(TuyaHaVacuum(device, device_manager))

    return entities



class TuyaHaVacuum(TuyaHaDevice, VacuumEntity):
    """Tuya Vacuum Device."""

    HASTATE = STATE_IDLE

    @property
    def name(self) -> str | None:
        """Return Tuya device name."""
        return self.tuya_device.name

    @property
    def status(self) -> str | None:
        """Return Tuya device state."""
        status = self.tuya_device.status.get(DPCODE_STATUS)
        if status == "standby":
            HASTATE = STATE_IDLE
            return "Standby"
        if status == "goto_charge":
            HASTATE = STATE_RETURNING
            return "Returning to Dock"
        if status == "charging":
            HASTATE = STATE_DOCKED
            return "Docked - Charging"
        if status == "charge_done":
            HASTATE = STATE_DOCKED
            return "Docked - Charged"
        HASTATE = STATE_CLEANING
        return "Cleaning"


    @property
    def battery_level(self) -> int | None:
        """Return Tuya device state."""
        return self.tuya_device.status.get("electricity_left")

    @property
    def state(self):
        return self.HASTATE
    
    @property
    def supported_features(self):
        """Flag supported features."""
        supports = 0
        supports = supports | SUPPORT_TURN_ON
        supports = supports | SUPPORT_TURN_OFF
        supports = supports | SUPPORT_PAUSE
        supports = supports | SUPPORT_RETURN_HOME
        supports = supports | SUPPORT_STATE
        supports = supports | SUPPORT_STATUS
        supports = supports | SUPPORT_STOP
        supports = supports | SUPPORT_BATTERY
        supports = supports | SUPPORT_START
        return supports



##Functions:##
    def turn_on(self, **kwargs: Any) -> None:
        """Turn the device on."""
        _LOGGER.debug("Turning on %s", self.name)

        self.tuya_device_manager.sendCommands(
            self.tuya_device.id, [{"code": DPCODE_POWER, "value": True}]
        )
    
    def start(self, **kwargs: Any) -> None:
        """Turn the device on."""
        _LOGGER.debug("Starting %s", self.name)

        self.tuya_device_manager.sendCommands(
            self.tuya_device.id, [{"code": DPCODE_POWER, "value": True}]
        )

## Turn off/pause/stop all do the same thing

    def turn_off(self, **kwargs: Any) -> None:
        """Turn the device off."""
        _LOGGER.debug("Turning off %s", self.name)
        self.tuya_device_manager.sendCommands(
            self.tuya_device.id, [{"code": DPCODE_POWER, "value": False}]
        )

    def stop(self, **kwargs: Any) -> None:
        """Turn the device off."""
        _LOGGER.debug("Stopping %s", self.name)
        self.tuya_device_manager.sendCommands(
            self.tuya_device.id, [{"code": DPCODE_POWER, "value": False}]
        )
    
    def pause(self, **kwargs: Any) -> None:
        """Turn the device off."""
        _LOGGER.debug("Pausing %s", self.name)
        self.tuya_device_manager.sendCommands(
            self.tuya_device.id, [{"code": DPCODE_POWER, "value": False}]
        )
    
    def start_pause(self, **kwargs: Any) -> None:
        """Start/Pause the device"""
        _LOGGER.debug("Start/Pausing %s", self.name)
        status = False
        status = self.tuya_device.status.get(DPCODE_POWER)
        self.tuya_device_manager.sendCommands(
            self.tuya_device.id, [{"code": DPCODE_POWER, "value": not status}]
        )

    def return_to_base(self, **kwargs: Any) -> None:
        """Return device to Dock"""
        _LOGGER.debug("Return to base device %s", self.name)
        self.tuya_device_manager.sendCommands(
            self.tuya_device.id, [{"code": DPCODE_MODE, "value": "chargego"}]
        )

How can i put this information on my tuya v2? Mine vaccum get just 3 entities..... could you help me?

@tsutsuku
Copy link
Collaborator

tsutsuku commented Jul 6, 2021

@freitas666 this code is in v1.3 and later versions. So what can I help you with?

@freitas666
Copy link

@freitas666 this code is in v1.3 and later versions. So what can I help you with?

Hello @tsutsuku ... well i put my vaccum robot on tuyav2 but for me show just one entity with 3 attributes.... and this code looks like more attritubutes... how can i use this code?

@timota
Copy link
Author

timota commented Jul 13, 2021

According to current vacuum code paused state should be supported in status

        """Flag supported features."""
        supports = 0
        if DPCODE_PAUSE in self.tuya_device.status:
            supports = supports | SUPPORT_PAUSE

most vacuums doesnt have that attribute or doesnt report it at all. (checked via virtual devices as well). But paused definitely supported on devices and can be set. Also that status reported in current state via mode

    status={
        'power_go': False, 
        'mode': 'paused', 
        'direction_control': 'forward', 
        'status': 'charge_done', 
        'electricity_left': 100, 
        'seek': True, 
        'suction': 'strong', 
        'clean_record': '00000000184', 
        'clean_area': 0, 
        'clean_time': 0, 
        'cistern': 'low'
    }, 

can that be changed please to check supported modes and not status, as i strongly believe than that much closer to true/real values/statuses.
Also, in your code you have annotation Turn off/pause/stop all do the same thing - this is not true. All those modes are doing completely different things:

  • turn_off - do turn_off
  • pause - pause current task, when unpaused - robot continue his current task without reseting counters
  • stop - stop current task and reset counters.

If you need more info please let me know.
Thanks

@tsutsuku
Copy link
Collaborator

@timota Welcome to create a PR, and I will review it.

@timota
Copy link
Author

timota commented Jul 14, 2021

i'm not a developer but will try my best.

@tsutsuku
Copy link
Collaborator

@wapsi
Copy link

wapsi commented Jul 22, 2021

This is wonderful, I got my robot vacuum cleaner to work in Home Assistant now, thanks!

Is it possible to extend the capabilities of the vacuum seen in the Home Assistant entity somehow? Now I can see only some of them like status, battery, start, stop but from Tuya IoT portal I can see more more parameters that can be set:

power | Boolean | "{true,false}"
power_go | Boolean | "{true,false}"
direction_control | Enum | {   "range": [     "forward",     "backward",     "turn_left",     "turn_right",     "stop"   ] }
mode | Enum | {   "range": [     "standby",     "smart",     "wall_follow",     "single",     "point",     "chargego"   ] }
seek | Boolean | "{true,false}"
suction | Enum | {   "range": [     "strong",     "normal",     "gentle"   ] }

Also: The Tuya Smart mobile app is able to show and reset the map where the vacuum has been cleaning and according the API documentation it should be possible to pull and reset the map also (https://developer.tuya.com/en/docs/iot/fsd?id=K9gf487ck1tlo):

request | Request data | Enum | {“range”:[“get_map”, “get_path”,“get_both”]}
reset_map | Map resetting | Boolean | {}

Is this possible to integrate this into Home Assistant entity card?

@alexdelprete
Copy link

this code is in v1.3 and later versions. So what can I help you with?

was this code ported to the tuya official integration? Because it recognised my vacuum (entity vacuum.robo), but I only have start/stop actions available. No status or anything else...

image

@mirkocampisi
Copy link

mirkocampisi commented Jan 20, 2022

Hi, I added Tuya integration for my vacuum cleaner and I got these entities:
tuya

The status of the entity vacuum.m520 is always unknown, but in Smart Life app it is correct.

Also, it would be useful to have the fault alarm sensor, as seen in the function definition list of the device on Tuya IoT Platform:

Fault Alarm - Fault Value: edge_sweep, middle_sweep, left_wheel, right_wheel, garbage_box, land_check, collision

It would be fantastic to have also the map of the cleaned areas!

Thank you!

ps: these are the device features from tuya IOT:

{
"result": {
"category": "sd",
"functions": [
{
"code": "power",
"desc": "{}",
"name": "开关",
"type": "Boolean",
"values": "{}"
},
{
"code": "power_go",
"desc": "{}",
"name": "清扫开关",
"type": "Boolean",
"values": "{}"
},
{
"code": "mode",
"desc": "{"range":["standby","smart","wall_follow","spiral","chargego","random"]}",
"name": "工作模式",
"type": "Enum",
"values": "{"range":["standby","smart","wall_follow","spiral","chargego","random"]}"
},
{
"code": "direction_control",
"desc": "{"range":["forward","backward","turn_left","turn_right","stop"]}",
"name": "清扫方向",
"type": "Enum",
"values": "{"range":["forward","backward","turn_left","turn_right","stop"]}"
},
{
"code": "seek",
"desc": "{}",
"name": "寻找机器",
"type": "Boolean",
"values": "{}"
}
],
"status": [
{
"code": "power",
"name": "开关",
"type": "Boolean",
"values": "{}"
},
{
"code": "power_go",
"name": "清扫开关",
"type": "Boolean",
"values": "{}"
},
{
"code": "mode",
"name": "工作模式",
"type": "Enum",
"values": "{"range":["standby","smart","wall_follow","spiral","chargego","random"]}"
},
{
"code": "direction_control",
"name": "清扫方向",
"type": "Enum",
"values": "{"range":["forward","backward","turn_left","turn_right","stop"]}"
},
{
"code": "electricity_left",
"name": "剩余电量",
"type": "Integer",
"values": "{"unit":"%","min":0,"max":100,"scale":0,"step":1}"
},
{
"code": "seek",
"name": "寻找机器",
"type": "Boolean",
"values": "{}"
},
{
"code": "clean_record",
"name": "清扫记录",
"type": "String",
"values": "{"maxlen":255}"
},
{
"code": "clean_area",
"name": "清扫面积",
"type": "Integer",
"values": "{"unit":"㎡","min":0,"max":9999,"scale":0,"step":1}"
},
{
"code": "clean_time",
"name": "清扫时间",
"type": "Integer",
"values": "{"unit":"分","min":0,"max":9999,"scale":0,"step":1}"
},
{
"code": "fault",
"name": "故障上报",
"type": "Bitmap",
"values": "{"label":["edge_sweep","middle_sweep","left_wheel","right_wheel","garbage_box","land_check","collision"]}"
}
]
},
"success": true,
"t": 1642669563197
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement Request new features
Projects
None yet
Development

No branches or pull requests

9 participants