Skip to content

Commit

Permalink
Add support for Xiaomi Mijia STYJ02YM (viomi.vacuum.v7) (#576)
Browse files Browse the repository at this point in the history
* Add support for Xiaomi Mijia STYJ02YM (viomi.vacuum.v7)

Based on findings done by @nqkdev - thanks a lot for help!
This is the initial PR to fix #550 - plenty of values are still unknown so any help is welcome!

* update README

* add help message to set_fan_speed

* Add state "idle not docked"

* Add support for a few more commands of the STYJ02YM (#581)

* STYJ02YM: Add support for DND

* STYJ02YM: Add support to change language.

* STYJ02YM: Add support to switch button LEDs on/off

* add carpetturbo as new method, add docstrings
  • Loading branch information
rytilahti authored Dec 4, 2019
1 parent 621aa85 commit 57c1009
Show file tree
Hide file tree
Showing 4 changed files with 271 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Supported devices
- Xiaomi Mi Air Purifier
- Xiaomi Aqara Camera
- Xiaomi Mijia 360 1080p
- Xiaomi Mijia STYJ02YM (Viomi)
- Xiaomi Mi Smart WiFi Socket
- Xiaomi Chuangmi Plug V1 (1 Socket, 1 USB Port)
- Xiaomi Chuangmi Plug V3 (1 Socket, 2 USB Ports)
Expand Down
1 change: 1 addition & 0 deletions miio/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
Timer,
VacuumStatus,
)
from miio.viomivacuum import ViomiVacuum
from miio.waterpurifier import WaterPurifier
from miio.wifirepeater import WifiRepeater
from miio.wifispeaker import WifiSpeaker
Expand Down
2 changes: 2 additions & 0 deletions miio/discovery.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
PowerStrip,
Toiletlid,
Vacuum,
ViomiVacuum,
WaterPurifier,
WifiRepeater,
WifiSpeaker,
Expand Down Expand Up @@ -149,6 +150,7 @@
"lumi-gateway-": lambda x: other_package_info(
x, "https://github.com/Danielhiversen/PyXiaomiGateway"
),
"viomi-vacuum-v7": ViomiVacuum,
} # type: Dict[str, Union[Callable, Device]]


Expand Down
267 changes: 267 additions & 0 deletions miio/viomivacuum.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,267 @@
import logging
from collections import defaultdict
from datetime import timedelta
from enum import Enum

import click

from .click_common import EnumType, command, format_output
from .device import Device
from .utils import pretty_seconds
from .vacuumcontainers import DNDStatus

_LOGGER = logging.getLogger(__name__)


class ViomiVacuumSpeed(Enum):
Silent = 0
Standard = 1
Medium = 2
Turbo = 3


class ViomiVacuumState(Enum):
IdleNotDocked = 0
Idle = 1
Idle2 = 2
Cleaning = 3
Returning = 4
Docked = 5


class ViomiLanguage(Enum):
CN = 1 # Chinese (default)
EN = 2 # English


class ViomiLedState(Enum):
Off = 0
On = 1


class ViomiCarpetTurbo(Enum):
Off = 0
Medium = 1
Turbo = 2


class ViomiVacuumStatus:
def __init__(self, data):
# ["run_state","mode","err_state","battary_life","box_type","mop_type","s_time","s_area",
# [ 5, 0, 2103, 85, 3, 1, 0, 0,
# "suction_grade","water_grade","remember_map","has_map","is_mop","has_newmap"]'
# 1, 11, 1, 1, 1, 0 ]
self.data = data

@property
def state(self):
"""State of the vacuum."""
return ViomiVacuumState(self.data["run_state"])

@property
def is_on(self) -> bool:
"""True if cleaning."""
return self.state == ViomiVacuumState.Cleaning

@property
def mode(self):
"""Active mode.
TODO: unknown values
"""
return self.data["mode"]

@property
def error(self):
"""Error code.
TODO: unknown values
"""
return self.data["error_state"]

@property
def battery(self) -> int:
"""Battery in percentage."""
return self.data["battary_life"]

@property
def box_type(self):
"""Box type.
TODO: unknown values"""
return self.data["box_type"]

@property
def mop_type(self):
"""Mop type.
TODO: unknown values"""
return self.data["mop_type"]

@property
def clean_time(self) -> timedelta:
"""Cleaning time."""
return pretty_seconds(self.data["s_time"])

@property
def clean_area(self) -> float:
"""Cleaned area.
TODO: unknown values
"""
return self.data["s_area"]

@property
def fanspeed(self) -> ViomiVacuumSpeed:
"""Current fan speed."""
return ViomiVacuumSpeed(self.data["suction_grade"])

@property
def water_level(self):
"""Tank's water level.
TODO: unknown values, percentage?
"""
return self.data["water_grade"]

@property
def remember_map(self) -> bool:
"""True to remember the map."""
return bool(self.data["remember_map"])

@property
def has_map(self) -> bool:
"""True if device has map?"""
return bool(self.data["has_map"])

@property
def has_new_map(self) -> bool:
"""TODO: unknown"""
return bool(self.data["has_newmap"])

@property
def is_mop(self) -> bool:
"""True if mopping."""
return bool(self.data["is_mop"])


class ViomiVacuum(Device):
"""Interface for Viomi vacuums (viomi.vacuum.v7)."""

@command(
default_output=format_output(
"",
"State: {result.state}\n"
"Mode: {result.mode}\n"
"Error: {result.error}\n"
"Battery: {result.battery}\n"
"Fan speed: {result.fanspeed}\n"
"Box type: {result.box_type}\n"
"Mop type: {result.mop_type}\n"
"Clean time: {result.clean_time}\n"
"Clean area: {result.clean_area}\n"
"Water level: {result.water_level}\n"
"Remember map: {result.remember_map}\n"
"Has map: {result.has_map}\n"
"Has new map: {result.has_new_map}\n"
"Is mop: {result.is_mop}\n",
)
)
def status(self) -> ViomiVacuumStatus:
"""Retrieve properties."""
properties = [
"run_state",
"mode",
"err_state",
"battary_life",
"box_type",
"mop_type",
"s_time",
"s_area",
"suction_grade",
"water_grade",
"remember_map",
"has_map",
"is_mop",
"has_newmap",
]

values = self.send("get_prop", properties)

return ViomiVacuumStatus(defaultdict(lambda: None, zip(properties, values)))

@command()
def start(self):
"""Start cleaning."""
# TODO figure out the parameters
self.send("set_mode_withroom", [0, 1, 0])

@command()
def stop(self):
"""Stop cleaning."""
self.send("set_mode", [0])

@command()
def pause(self):
"""Pause cleaning."""
self.send("set_mode_withroom", [0, 2, 0])

@command(click.argument("speed", type=str))
def set_fan_speed(self, speed: str):
"""Set fanspeed [silent, standard, medium, turbo]."""
self.send("set_suction", [ViomiVacuumSpeed(speed.capitalize()).value])

@command()
def home(self):
"""Return to home."""
self.send("set_charge", [1])

@command()
def dnd_status(self):
"""Returns do-not-disturb status."""
status = self.send("get_notdisturb")
return DNDStatus(
dict(
enabled=status[0],
start_hour=status[1],
start_minute=status[2],
end_hour=status[3],
end_minute=status[4],
)
)

@command(
click.option("--disable", is_flag=True),
click.argument("start_hr", type=int),
click.argument("start_min", type=int),
click.argument("end_hr", type=int),
click.argument("end_min", type=int),
)
def set_dnd(
self, disable: bool, start_hr: int, start_min: int, end_hr: int, end_min: int
):
"""Set do-not-disturb.
:param int start_hr: Start hour
:param int start_min: Start minute
:param int end_hr: End hour
:param int end_min: End minute"""
return self.send(
"set_notdisturb",
[0 if disable else 1, start_hr, start_min, end_hr, end_min],
)

@command(click.argument("language", type=EnumType(ViomiLanguage, False)))
def set_language(self, language: ViomiLanguage):
"""Set the device's audio language."""
return self.send("set_language", [language.value])

@command(click.argument("state", type=EnumType(ViomiLedState, False)))
def led(self, state: ViomiLedState):
"""Switch the button leds on or off."""
return self.send("set_light", [state.value])

@command(click.argument("mode", type=EnumType(ViomiCarpetTurbo)))
def carpet_mode(self, mode: ViomiCarpetTurbo):
"""Set the carpet mode."""
return self.send("set_carpetturbo", [mode.value])

6 comments on commit 57c1009

@merccooper
Copy link

@merccooper merccooper commented on 57c1009 Dec 20, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

box_type = which box is installed
[1] - vacuum tank
[2] - water tank (just water)
[3] - 2in1 water tank (mop and vacuum)

water_grade refers to the water used by the vacuum while mopping
[11] - low
[12] - medium
[13] - high
water_grade is also controlled by the command set_suction

@merccooper
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've also got all the error descriptions digging in the android app, and also found 2 commands that could help changing the water level, but I can't get them to work
"waterSet": "Water level", "waterGrade": ["Low", "Medium", "High"],

@merccooper
Copy link

@merccooper merccooper commented on 57c1009 Dec 20, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

set_mop - will set the vacuum mode (vacuum and mop, vacuum, mop)
[0] - vacuum
[1] - vacuum and mop
[2] - mop

mop_type will return the value of the current vacuum mode

@merccooper
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s_area returns the amount of square meters cleaned (last cleaning session) ex.{"result":[11.230000],"id":837}= 11 sqm
s_time = I guess it gives you the time that the vacuum spent cleaning (last session)

@Ciubas
Copy link

@Ciubas Ciubas commented on 57c1009 Jan 12, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How can I add this to home assistant files? :)

@Hellcube
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

set_mop

mop_type will not retunr value of current vacuum mode. is_mop will.

Please sign in to comment.