Skip to content

Commit

Permalink
Add consumable status to viomi vacuum (#805)
Browse files Browse the repository at this point in the history
* Add consumable status to viomi

* Fix Lint

* Fix lint
  • Loading branch information
titilambert committed Sep 1, 2020
1 parent 425efb3 commit 93b7a77
Show file tree
Hide file tree
Showing 11 changed files with 112 additions and 40 deletions.
4 changes: 2 additions & 2 deletions .github/ISSUE_TEMPLATE/bug_report.md
Expand Up @@ -16,13 +16,13 @@ A clear and concise description of what the bug is.

**Device information:**
If the issue is specific to a device [Use `miiocli device --ip <ip address> --token <token>`]:
- Model:
- Model:
- Hardware version:
- Firmware version:

**To Reproduce**
Steps to reproduce the behavior:
1.
1.

**Expected behavior**
A clear and concise description of what you expected to happen.
Expand Down
4 changes: 2 additions & 2 deletions docs/vacuum.rst
Expand Up @@ -31,7 +31,7 @@ Status reporting
Cleaning since: 0:00:00
Cleaned area: 0.0 m²
Water box attached: False

Start cleaning
~~~~~~~~~~~~~~

Expand Down Expand Up @@ -66,7 +66,7 @@ State of consumables
Side brush: 2 days, 16:14:00 (left 5 days, 15:46:00)
Filter: 2 days, 16:14:00 (left 3 days, 13:46:00)
Sensor dirty: 2:37:48 (left 1 day, 3:22:12)

Schedule information
~~~~~~~~~~~~~~~~~~~~

Expand Down
2 changes: 1 addition & 1 deletion miio/airpurifier_miot.py
Expand Up @@ -361,7 +361,7 @@ def set_mode(self, mode: OperationMode):
)
def set_favorite_level(self, level: int):
"""Set the favorite level used when the mode is `favorite`,
should be between 0 and 14.
should be between 0 and 14.
"""
if level < 0 or level > 14:
raise AirPurifierMiotException("Invalid favorite level: %s" % level)
Expand Down
15 changes: 7 additions & 8 deletions miio/alarmclock.py
Expand Up @@ -86,8 +86,7 @@ def get_config_version(self):

@command()
def clock_system(self) -> HourlySystem:
"""Returns either 12 or 24 depending on which system is in use.
"""
"""Returns either 12 or 24 depending on which system is in use."""
return HourlySystem(self.send("get_hourly_system")[0])

@command(click.argument("brightness", type=EnumType(HourlySystem)))
Expand Down Expand Up @@ -138,9 +137,9 @@ def get_ring(self, alarm_type: AlarmType):
def set_ring(self, alarm_type: AlarmType, ring: RingTone):
"""Set alarm tone.
-> 192.168.0.128 data= {"id":236,"method":"set_ring",
"params":[{"ringtone":"a1.mp3","smart_clock":"","type":"alarm"}]}
<- 192.168.0.57 data= {"result":["OK"],"id":236}
-> 192.168.0.128 data= {"id":236,"method":"set_ring",
"params":[{"ringtone":"a1.mp3","smart_clock":"","type":"alarm"}]}
<- 192.168.0.57 data= {"result":["OK"],"id":236}
"""
raise NotImplementedError()
# return self.send("set_ring", ) == ["OK"]
Expand Down Expand Up @@ -192,7 +191,7 @@ def near_wakeup(self):
@command()
def countdown(self):
"""
-> 192.168.0.128 data= {"id":258,"method":"get_count_down_v2","params":[]}
-> 192.168.0.128 data= {"id":258,"method":"get_count_down_v2","params":[]}
"""
return self.send("get_count_down_v2")

Expand Down Expand Up @@ -265,8 +264,8 @@ def start_countdown(self, url):
@command()
def query(self):
"""
-> 192.168.0.128 data= {"id":227,"method":"alarm_ops","params":
{"operation":"query","index":0,"update_datetime":1564205198413,"req_type":"reminder"}}
-> 192.168.0.128 data= {"id":227,"method":"alarm_ops","params":
{"operation":"query","index":0,"update_datetime":1564205198413,"req_type":"reminder"}}
"""

Expand Down
2 changes: 1 addition & 1 deletion miio/discovery.py
Expand Up @@ -209,7 +209,7 @@ def __init__(self):

def check_and_create_device(self, info, addr) -> Optional[Device]:
"""Create a corresponding :class:`Device` implementation
for a given info and address.."""
for a given info and address.."""
name = info.name
for identifier, v in DEVICE_MAP.items():
if name.startswith(identifier):
Expand Down
2 changes: 1 addition & 1 deletion miio/exceptions.py
Expand Up @@ -23,7 +23,7 @@ class DeviceError(DeviceException):
The device given error code and message can be accessed with
`code` and `message` variables.
"""
"""

def __init__(self, error):
self.code = error.get("code")
Expand Down
8 changes: 4 additions & 4 deletions miio/extract_tokens.py
Expand Up @@ -168,10 +168,10 @@ def read_tokens(self, db) -> Iterator[DeviceConfig]:
@click.option("--dump-raw", is_flag=True, help="dumps raw rows")
def main(backup, write_to_disk, password, dump_all, dump_raw):
"""Reads device information out from an sqlite3 DB.
If the given file is an Android backup (.ab), the database
will be extracted automatically.
If the given file is an iOS backup, the tokens will be
extracted (and decrypted if needed) automatically.
If the given file is an Android backup (.ab), the database
will be extracted automatically.
If the given file is an iOS backup, the tokens will be
extracted (and decrypted if needed) automatically.
"""

def read_miio_database(tar):
Expand Down
39 changes: 22 additions & 17 deletions miio/gateway.py
Expand Up @@ -752,7 +752,8 @@ def set_night_light_color(self, color_name: str):
return self.set_night_light(current_brightness, color_map[color_name])

@command(
click.argument("color_name", type=str), click.argument("brightness", type=int),
click.argument("color_name", type=str),
click.argument("brightness", type=int),
)
def set_rgb_using_name(self, color_name: str, brightness: int):
"""Set gateway light color (using color name, 'color_map' variable in the source holds the valid values) and brightness (0-100)."""
Expand All @@ -768,7 +769,8 @@ def set_rgb_using_name(self, color_name: str, brightness: int):
return self.set_rgb(brightness, color_map[color_name])

@command(
click.argument("color_name", type=str), click.argument("brightness", type=int),
click.argument("color_name", type=str),
click.argument("brightness", type=int),
)
def set_night_light_using_name(self, color_name: str, brightness: int):
"""Set night light color (using color name, 'color_map' variable in the source holds the valid values) and brightness (0-100)."""
Expand Down Expand Up @@ -798,7 +800,11 @@ class SubDevice:
class props:
"""Defines properties of the specific device."""

def __init__(self, gw: Gateway = None, dev_info: SubDeviceInfo = None,) -> None:
def __init__(
self,
gw: Gateway = None,
dev_info: SubDeviceInfo = None,
) -> None:
self._gw = gw
self.sid = dev_info.sid
self._battery = None
Expand All @@ -811,18 +817,15 @@ def __init__(self, gw: Gateway = None, dev_info: SubDeviceInfo = None,) -> None:
self.type = DeviceType.Unknown

def __repr__(self):
return (
"<Subdevice %s: %s, model: %s, zigbee: %s, fw: %s, bat: %s, vol: %s, props: %s>"
% (
self.device_type,
self.sid,
self.model,
self.zigbee_model,
self.firmware_version,
self.get_battery(),
self.get_voltage(),
self.status,
)
return "<Subdevice %s: %s, model: %s, zigbee: %s, fw: %s, bat: %s, vol: %s, props: %s>" % (
self.device_type,
self.sid,
self.model,
self.zigbee_model,
self.firmware_version,
self.get_battery(),
self.get_voltage(),
self.status,
)

@property
Expand Down Expand Up @@ -954,7 +957,8 @@ def get_battery(self):
self._battery = self.send("get_battery").pop()
else:
_LOGGER.info(
"Gateway model '%s' does not (yet) support get_battery", self._gw.model,
"Gateway model '%s' does not (yet) support get_battery",
self._gw.model,
)
return self._battery

Expand All @@ -965,7 +969,8 @@ def get_voltage(self):
self._voltage = self.get_property("voltage").pop() / 1000
else:
_LOGGER.info(
"Gateway model '%s' does not (yet) support get_voltage", self._gw.model,
"Gateway model '%s' does not (yet) support get_voltage",
self._gw.model,
)
return self._voltage

Expand Down
2 changes: 1 addition & 1 deletion miio/vacuum_cli.py
Expand Up @@ -587,7 +587,7 @@ def update_firmware(vac: miio.Vacuum, url: str, md5: str, ip: str):
`--ip` can be used to override automatically detected IP address for
the device to contact for the update.
"""
"""

# TODO Check that the device is in updateable state.

Expand Down
2 changes: 1 addition & 1 deletion miio/vacuumcontainers.py
Expand Up @@ -274,7 +274,7 @@ def error(self) -> str:
def complete(self) -> bool:
"""Return True if the cleaning run was complete (e.g. without errors).
see also :func:`error`."""
see also :func:`error`."""
return bool(self.data[5] == 1)

def __repr__(self) -> str:
Expand Down
72 changes: 70 additions & 2 deletions miio/viomivacuum.py
Expand Up @@ -3,14 +3,14 @@
from collections import defaultdict
from datetime import timedelta
from enum import Enum
from typing import Dict, Optional
from typing import Dict, List, Optional

import click

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

_LOGGER = logging.getLogger(__name__)

Expand Down Expand Up @@ -41,6 +41,69 @@
}


class ViomiConsumableStatus(ConsumableStatus):
def __init__(self, data: List[int]) -> None:
# [17, 17, 17, 17]
self.data = [d * 60 * 60 for d in data]
self.side_brush_total = timedelta(hours=180)
self.main_brush_total = timedelta(hours=360)
self.filter_total = timedelta(hours=180)
self.mop_total = timedelta(hours=180)

@property
def main_brush(self) -> timedelta:
"""Main brush usage time."""
return pretty_seconds(self.data[0])

@property
def main_brush_left(self) -> timedelta:
"""How long until the main brush should be changed."""
return self.main_brush_total - self.main_brush

@property
def side_brush(self) -> timedelta:
"""Side brush usage time."""
return pretty_seconds(self.data[1])

@property
def side_brush_left(self) -> timedelta:
"""How long until the side brush should be changed."""
return self.side_brush_total - self.side_brush

@property
def filter(self) -> timedelta:
"""Filter usage time."""
return pretty_seconds(self.data[2])

@property
def filter_left(self) -> timedelta:
"""How long until the filter should be changed."""
return self.filter_total - self.filter

@property
def mop(self) -> timedelta:
"""Return ``sensor_dirty_time``"""
return pretty_seconds(self.data[3])

@property
def mop_left(self) -> timedelta:
return self.sensor_dirty_total - self.sensor_dirty

def __repr__(self) -> str:
return (
"<ConsumableStatus main: %s, side: %s, filter: %s, mop: %s>"
% ( # noqa: E501
self.main_brush,
self.side_brush,
self.filter,
self.mop,
)
)

def __json__(self):
return self.data


class ViomiVacuumSpeed(Enum):
Silent = 0
Standard = 1
Expand Down Expand Up @@ -317,6 +380,11 @@ def clean_mode(self, mode):
def mop_mode(self, mop_mode):
self.send("set_moproute", [mop_mode.value])

@command()
def consumable_status(self) -> ViomiConsumableStatus:
"""Return information about consumables."""
return ViomiConsumableStatus(self.send("get_consumables"))

@command()
def dnd_status(self):
"""Returns do-not-disturb status."""
Expand Down

0 comments on commit 93b7a77

Please sign in to comment.