Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
4e63252
Added Xiaomi Vaccum Mop 2 Ultra and Pro+
2pirko Mar 13, 2022
6e8f278
Updated automatic formatting using black
2pirko Mar 13, 2022
e21387c
Removed duplicated supported models
2pirko Mar 13, 2022
469cc89
Extended dreame test to test all supported models
2pirko Mar 13, 2022
2db536c
Added test for invalid dreame model
2pirko Mar 13, 2022
05a6ea1
Formatting by black
2pirko Mar 13, 2022
2db26f4
import isort
2pirko Mar 13, 2022
7723bfa
Updated readme with newly supported models
2pirko Mar 14, 2022
2dd5e61
Added support for Vacuum interface: VaccumDevice and VacuumMiotDevice
2pirko Mar 20, 2022
c354dd4
Merge branch 'master' into vaccum_device
2pirko Mar 20, 2022
dc1ccfa
Fixed isort
2pirko Mar 20, 2022
5ba5374
Removing unnecessary pass
2pirko Mar 20, 2022
866e856
Feedback from PR: VaccumDevice renamed to VacuumInterface
2pirko Apr 7, 2022
4f7c5da
Step2: VacuumInterface no moreinherits Device
2pirko Apr 7, 2022
c25552f
Unit test fixed
2pirko Apr 7, 2022
4910920
Merge branch 'master' into vaccum_device
2pirko Apr 7, 2022
4ce7c75
Merge branch 'master' into vaccum_device
2pirko Apr 22, 2022
c5ad8c8
Merge branch 'master' into vaccum_device
2pirko Apr 24, 2022
1ac8138
VaccumInterface published as symbol available for the "interface" pac…
2pirko Apr 25, 2022
01406ea
Merge branch 'master' into vaccum_device
2pirko Apr 25, 2022
af8fffb
Added two methods into VacuumInterface:
2pirko Apr 25, 2022
580b94f
Added vacuum unit test
2pirko Apr 25, 2022
ccad8fb
Merge branch 'master' into vacuum_interface_fan_speed_presets
2pirko Apr 25, 2022
a3a8ee0
Fixed python 3.10
2pirko Apr 25, 2022
f7065e3
Imporved test coverage
2pirko Apr 25, 2022
ebdd15f
Additional increase of test coverage
2pirko Apr 25, 2022
9ec88b0
Added test for param validation in set_fan_speed_preset()
2pirko Apr 25, 2022
3da873d
unit test simplification
2pirko Apr 25, 2022
74f8452
Feedback from PR
2pirko Apr 26, 2022
3465302
Minor DOCS improvement
2pirko Apr 26, 2022
410f5e7
Minor docs update
2pirko Apr 26, 2022
5db1b58
Feedback from PR
2pirko Apr 28, 2022
8c52d44
Merge branch 'master' into vacuum_interface_fan_speed_presets2
2pirko May 2, 2022
b7cc703
Updated noqa codes for new flake8 version
2pirko May 2, 2022
c7ebce0
remove copyright notice update
rytilahti May 23, 2022
372702a
Rework docstrings
rytilahti May 23, 2022
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
2 changes: 1 addition & 1 deletion devtools/containers.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ class ModelMapping(DataClassJsonMixin):
def urn_for_model(self, model: str):
matches = [inst for inst in self.instances if inst.model == model]
if len(matches) > 1:
print( # noqa: T001
print( # noqa: T201
"WARNING more than a single match for model %s, using the first one: %s"
% (model, matches)
)
Expand Down
4 changes: 2 additions & 2 deletions devtools/parse_pcap.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ def read_payloads_from_file(file, tokens: list[str]):

yield src_addr, dst_addr, payload

print(stats) # noqa: T001
print(stats) # noqa: T201


@app.command()
Expand All @@ -77,7 +77,7 @@ def read_file(
):
"""Read PCAP file and output decrypted miio communication."""
for src_addr, dst_addr, payload in read_payloads_from_file(file, token):
print(f"{src_addr:<15} -> {dst_addr:<15} {payload}") # noqa: T001
print(f"{src_addr:<15} -> {dst_addr:<15} {payload}") # noqa: T201


if __name__ == "__main__":
Expand Down
15 changes: 12 additions & 3 deletions miio/integrations/vacuum/dreame/dreamevacuum_miot.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

from miio.click_common import command, format_output
from miio.exceptions import DeviceException
from miio.interfaces import VacuumInterface
from miio.interfaces import FanspeedPresets, VacuumInterface
from miio.miot_device import DeviceStatus as DeviceStatusContainer
from miio.miot_device import MiotDevice, MiotMapping

Expand Down Expand Up @@ -527,13 +527,22 @@ def set_fan_speed(self, speed: int):
return self.set_property("cleaning_mode", fanspeed.value)

@command()
def fan_speed_presets(self) -> Dict[str, int]:
"""Return dictionary containing supported fan speeds."""
def fan_speed_presets(self) -> FanspeedPresets:
"""Return available fan speed presets."""
fanspeeds_enum = _get_cleaning_mode_enum_class(self.model)
if not fanspeeds_enum:
return {}
return _enum_as_dict(fanspeeds_enum)

@command(click.argument("speed", type=int))
def set_fan_speed_preset(self, speed_preset: int) -> None:
"""Set fan speed preset speed."""
if speed_preset not in self.fan_speed_presets().values():
raise ValueError(
f"Invalid preset speed {speed_preset}, not in: {self.fan_speed_presets().values()}"
)
self.set_fan_speed(speed_preset)

@command()
def waterflow(self):
"""Get water flow setting."""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,10 @@ def test_fan_speed(self):
value = self.device.fan_speed()
assert value == {"Medium": 2}

def test_set_fan_speed_preset(self):
for speed in self.device.fan_speed_presets().values():
self.device.set_fan_speed_preset(speed)


@pytest.mark.usefixtures("dummydreamef9vacuum")
class TestDreameF9Vacuum(TestCase):
Expand Down Expand Up @@ -253,10 +257,7 @@ def test_waterflow(self):

@pytest.mark.parametrize("model", MIOT_MAPPING.keys())
def test_dreame_models(model: str):
vac = DreameVacuum(model=model)
# test _get_cleaning_mode_enum_class returns non-empty mapping
fp = vac.fan_speed_presets()
assert (fp is not None) and (len(fp) > 0)
DreameVacuum(model=model)


def test_invalid_dreame_model():
Expand Down
16 changes: 15 additions & 1 deletion miio/integrations/vacuum/mijia/g1vacuum.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import click

from miio.click_common import EnumType, command, format_output
from miio.interfaces import VacuumInterface
from miio.interfaces import FanspeedPresets, VacuumInterface
from miio.miot_device import DeviceStatus, MiotDevice

_LOGGER = logging.getLogger(__name__)
Expand Down Expand Up @@ -373,3 +373,17 @@ def consumable_reset(self, consumable: G1Consumable):
def set_fan_speed(self, fan_speed: G1FanSpeed):
"""Set fan speed."""
return self.set_property("fan_speed", fan_speed.value)

@command()
def fan_speed_presets(self) -> FanspeedPresets:
"""Return available fan speed presets."""
return {x.name: x.value for x in G1FanSpeed}

@command(click.argument("speed", type=int))
def set_fan_speed_preset(self, speed_preset: int) -> None:
"""Set fan speed preset speed."""
if speed_preset not in self.fan_speed_presets().values():
raise ValueError(
f"Invalid preset speed {speed_preset}, not in: {self.fan_speed_presets().values()}"
)
return self.set_property("fan_speed", speed_preset)
17 changes: 13 additions & 4 deletions miio/integrations/vacuum/roborock/vacuum.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import os
import pathlib
import time
from typing import Dict, List, Optional, Type, Union
from typing import List, Optional, Type, Union

import click
import pytz
Expand All @@ -22,7 +22,7 @@
)
from miio.device import Device, DeviceInfo
from miio.exceptions import DeviceException, DeviceInfoUnavailableException
from miio.interfaces import VacuumInterface
from miio.interfaces import FanspeedPresets, VacuumInterface

from .vacuumcontainers import (
CarpetModeStatus,
Expand Down Expand Up @@ -619,8 +619,8 @@ def fan_speed(self):
return self.send("get_custom_mode")[0]

@command()
def fan_speed_presets(self) -> Dict[str, int]:
"""Return dictionary containing supported fan speeds."""
def fan_speed_presets(self) -> FanspeedPresets:
"""Return available fan speed presets."""

def _enum_as_dict(cls):
return {x.name: x.value for x in list(cls)}
Expand Down Expand Up @@ -652,6 +652,15 @@ def _enum_as_dict(cls):

return _enum_as_dict(fanspeeds)

@command(click.argument("speed", type=int))
def set_fan_speed_preset(self, speed_preset: int) -> None:
"""Set fan speed preset speed."""
if speed_preset not in self.fan_speed_presets().values():
raise ValueError(
f"Invalid preset speed {speed_preset}, not in: {self.fan_speed_presets().values()}"
)
return self.send("set_custom_mode", [speed_preset])

@command()
def sound_info(self):
"""Get voice settings."""
Expand Down
16 changes: 15 additions & 1 deletion miio/integrations/vacuum/roidmi/roidmivacuum_miot.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

from miio.click_common import EnumType, command
from miio.integrations.vacuum.roborock.vacuumcontainers import DNDStatus
from miio.interfaces import VacuumInterface
from miio.interfaces import FanspeedPresets, VacuumInterface
from miio.miot_device import DeviceStatus, MiotDevice, MiotMapping

_LOGGER = logging.getLogger(__name__)
Expand Down Expand Up @@ -637,6 +637,20 @@ def set_fanspeed(self, fanspeed_mode: FanSpeed):
"""Set fan speed."""
return self.set_property("fanspeed_mode", fanspeed_mode.value)

@command()
def fan_speed_presets(self) -> FanspeedPresets:
"""Return available fan speed presets."""
return {"Sweep": 0, "Silent": 1, "Basic": 2, "Strong": 3, "FullSpeed": 4}

@command(click.argument("speed", type=int))
def set_fan_speed_preset(self, speed_preset: int) -> None:
"""Set fan speed preset speed."""
if speed_preset not in self.fan_speed_presets().values():
raise ValueError(
f"Invalid preset speed {speed_preset}, not in: {self.fan_speed_presets().values()}"
)
return self.set_property("fanspeed_mode", speed_preset)

@command(click.argument("sweep_type", type=EnumType(SweepType)))
def set_sweep_type(self, sweep_type: SweepType):
"""Set sweep_type."""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,10 @@ def test_parse_forbid_mode2(self):
)
assert str(status._parse_forbid_mode(value)) == str(expected_value)

def test_set_fan_speed_preset(self):
for speed in self.device.fan_speed_presets().values():
self.device.set_fan_speed_preset(speed)


class DummyRoidmiVacuumMiot2(DummyMiotDevice, RoidmiVacuumMiot):
def __init__(self, *args, **kwargs):
Expand Down
21 changes: 15 additions & 6 deletions miio/integrations/vacuum/viomi/viomivacuum.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
ConsumableStatus,
DNDStatus,
)
from miio.interfaces import VacuumInterface
from miio.interfaces import FanspeedPresets, VacuumInterface
from miio.utils import pretty_seconds

_LOGGER = logging.getLogger(__name__)
Expand Down Expand Up @@ -338,11 +338,6 @@ def fanspeed(self) -> ViomiVacuumSpeed:
"""Current fan speed."""
return ViomiVacuumSpeed(self.data["suction_grade"])

@command()
def fan_speed_presets(self) -> Dict[str, int]:
"""Return dictionary containing supported fanspeeds."""
return {x.name: x.value for x in list(ViomiVacuumSpeed)}

@property
def water_grade(self) -> ViomiWaterGrade:
"""Water grade."""
Expand Down Expand Up @@ -677,6 +672,20 @@ def set_fan_speed(self, speed: ViomiVacuumSpeed):
"""Set fanspeed [silent, standard, medium, turbo]."""
self.send("set_suction", [speed.value])

@command()
def fan_speed_presets(self) -> FanspeedPresets:
"""Return available fan speed presets."""
return {x.name: x.value for x in list(ViomiVacuumSpeed)}

@command(click.argument("speed", type=int))
def set_fan_speed_preset(self, speed_preset: int) -> None:
"""Set fan speed preset speed."""
if speed_preset not in self.fan_speed_presets().values():
raise ValueError(
f"Invalid preset speed {speed_preset}, not in: {self.fan_speed_presets().values()}"
)
self.send("set_suction", [speed_preset])

@command(click.argument("watergrade", type=EnumType(ViomiWaterGrade)))
def set_water_grade(self, watergrade: ViomiWaterGrade):
"""Set water grade.
Expand Down
4 changes: 2 additions & 2 deletions miio/interfaces/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""Interfaces API."""

from .vacuuminterface import VacuumInterface
from .vacuuminterface import FanspeedPresets, VacuumInterface

__all__ = ["VacuumInterface"]
__all__ = ["FanspeedPresets", "VacuumInterface"]
29 changes: 26 additions & 3 deletions miio/interfaces/vacuuminterface.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,46 @@
"""`VacuumInterface` is an interface (abstract class) with shared API for all vacuum
devices."""
from abc import abstractmethod
from typing import Dict

# Dictionary of predefined fan speeds
FanspeedPresets = Dict[str, int]


class VacuumInterface:
"""Vacuum API interface."""

@abstractmethod
def home(self):
"""Return to home."""
"""Return vacuum robot to home station/dock."""

@abstractmethod
def start(self):
"""Start cleaning."""

@abstractmethod
def stop(self):
"""Validate that Stop cleaning."""
"""Stop cleaning."""

def pause(self):
"""Pause cleaning."""
"""Pause cleaning.

:raises RuntimeError: if the method is not supported by the device
"""
raise RuntimeError("`pause` not supported")

@abstractmethod
def fan_speed_presets(self) -> FanspeedPresets:
"""Return available fan speed presets.

The returned object is a dictionary where the key is user-readable name and the
value is input for :func:`set_fan_speed_preset()`.
"""

@abstractmethod
def set_fan_speed_preset(self, speed_preset: int) -> None:
"""Set fan speed preset speed.

:param speed_preset: a value from :func:`fan_speed_presets()`
:raises ValueError: for invalid preset value
"""
55 changes: 55 additions & 0 deletions miio/tests/test_vacuums.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
"""Test of vacuum devices."""
from collections.abc import Iterable
from typing import List, Sequence, Tuple, Type

import pytest

from miio.device import Device
from miio.integrations.vacuum.roborock.vacuum import ROCKROBO_V1
from miio.interfaces import VacuumInterface

# list of all supported vacuum classes
VACUUM_CLASSES: Tuple[Type[VacuumInterface], ...] = tuple(
cl for cl in VacuumInterface.__subclasses__() # type: ignore
)


def _all_vacuum_models() -> Sequence[Tuple[Type[Device], str]]:
""":return: list of tuples with supported vacuum models with corresponding class"""
result: List[Tuple[Type[Device], str]] = []
for cls in VACUUM_CLASSES:
assert issubclass(cls, Device)
vacuum_models = cls.supported_models
assert isinstance(vacuum_models, Iterable)
for model in vacuum_models:
result.append((cls, model))
return result # type: ignore


@pytest.mark.parametrize("cls, model", _all_vacuum_models())
def test_vacuum_fan_speed_presets(cls: Type[Device], model: str) -> None:
"""Test method VacuumInterface.fan_speed_presets()"""
if model == ROCKROBO_V1:
return # this model cannot be tested because presets depends on firmware
dev = cls("127.0.0.1", "68ffffffffffffffffffffffffffffff", model=model)
assert isinstance(dev, VacuumInterface)
presets = dev.fan_speed_presets()
assert presets is not None, "presets must be defined"
assert bool(presets), "presets cannot be empty"
assert isinstance(presets, dict), "presets must be dictionary"
for name, value in presets.items():
assert isinstance(name, str), "presets key must be string"
assert name, "presets key cannot be empty"
assert isinstance(value, int), "presets value must be integer"
assert value >= 0, "presets value must be >= 0"


@pytest.mark.parametrize("cls, model", _all_vacuum_models())
def test_vacuum_set_fan_speed_presets_fails(cls: Type[Device], model: str) -> None:
"""Test method VacuumInterface.fan_speed_presets()"""
if model == ROCKROBO_V1:
return # this model cannot be tested because presets depends on firmware
dev = cls("127.0.0.1", "68ffffffffffffffffffffffffffffff", model=model)
assert isinstance(dev, VacuumInterface)
with pytest.raises(ValueError):
dev.set_fan_speed_preset(-1)