From f14df412766dfc4d444034efb7b4f346c21e6fcb Mon Sep 17 00:00:00 2001 From: Teemu Rytilahti Date: Sun, 15 Mar 2020 22:04:12 +0100 Subject: [PATCH 1/3] Add fan_speed_presets() for querying available fan speeds This returns a dictionary {name, value} of the available fan speeds, the main driver being the need to simplify homeassistant integrations for different devices. For viomi vacuums this is currently straightforward and hard-coded, but we do special handling for rockrobo devices (based on info() responses): * If the query succeeds, newer firmware versions (3.5.7+) of v1 are handled as a special case, otherwise the new-style [100-105] mapping is returned. * If the query fails, we fall back to rockrobo v1's percentage-based mapping. This happens, e.g., when the vacuum has no cloud connectivity. Related to #523 Related downstream issues https://github.com/home-assistant/core/pull/32821 https://github.com/home-assistant/core/issues/31268 https://github.com/home-assistant/core/issues/27268 --- miio/vacuum.py | 63 ++++++++++++++++++++++++++++++++++++++++++++- miio/viomivacuum.py | 6 +++++ 2 files changed, 68 insertions(+), 1 deletion(-) diff --git a/miio/vacuum.py b/miio/vacuum.py index 0ee49cf51..91412872b 100644 --- a/miio/vacuum.py +++ b/miio/vacuum.py @@ -6,7 +6,7 @@ import os import pathlib import time -from typing import List, Optional, Union +from typing import Dict, List, Optional, Union import click import pytz @@ -46,6 +46,24 @@ class Consumable(enum.Enum): SensorDirty = "sensor_dirty_time" +class FanspeedV1(enum.Enum): + Silent = 38 + Standard = 60 + Medium = 77 + Turbo = 90 + + +class FanspeedV2(enum.Enum): + Silent = 101 + Standard = 102 + Medium = 103 + Turbo = 104 + Gentle = 105 + + +ROCKROBO_V1 = "rockrobo.vacuum.v1" + + class Vacuum(Device): """Main class representing the vacuum.""" @@ -54,6 +72,8 @@ def __init__( ) -> None: super().__init__(ip, token, start_id, debug) self.manual_seqnum = -1 + self.model = None + self._fanspeeds = FanspeedV1 @command() def start(self): @@ -416,6 +436,47 @@ def fan_speed(self): """Return fan speed.""" return self.send("get_custom_mode")[0] + def _autodetect_model(self): + """Detect the model of the vacuum. + + For the moment this is used only for the fanspeeds, + but that could be extended to cover other supported features.""" + # cloud-blocked vacuums will not return proper payloads + try: + info = self.info() + self.model = info.model + except TypeError: + self._fanspeeds = FanspeedV1 + self.model = ROCKROBO_V1 + _LOGGER.debug("Unable to query model, falling back to %s", self._fanspeeds) + return + + _LOGGER.info("model: %s", self.model) + + if info.model == ROCKROBO_V1: + _LOGGER.info("Got robov1, checking for firmware version") + fw_version = info.firmware_version + version, build = fw_version.split("_") + version = tuple(map(int, version.split("."))) + if version >= (3, 5, 7): + self._fanspeeds = FanspeedV2 + else: + self._fanspeeds = FanspeedV1 + else: + self._fanspeeds = FanspeedV2 + + _LOGGER.debug( + "Using new fanspeed mapping %s for %s", self._fanspeeds, info.model + ) + + @command() + def supported_fanspeeds(self) -> Dict[str, int]: + """Return dictionary containing supported fanspeeds.""" + if self.model is None: + self._autodetect_model() + + return {x.name: x.value for x in list(self._fanspeeds)} + @command() def sound_info(self): """Get voice settings.""" diff --git a/miio/viomivacuum.py b/miio/viomivacuum.py index 67d370380..75ea871aa 100644 --- a/miio/viomivacuum.py +++ b/miio/viomivacuum.py @@ -3,6 +3,7 @@ from collections import defaultdict from datetime import timedelta from enum import Enum +from typing import Dict import click @@ -338,3 +339,8 @@ def led(self, state: ViomiLedState): def carpet_mode(self, mode: ViomiCarpetTurbo): """Set the carpet mode.""" return self.send("set_carpetturbo", [mode.value]) + + @command() + def supported_fanspeeds(self) -> Dict[str, int]: + """Return dictionary containing supported fanspeeds.""" + return {x.name: x.value for x in list(ViomiVacuumSpeed)} From 2799a16005a29506320fef0da2662c1b8ce9358a Mon Sep 17 00:00:00 2001 From: Teemu Rytilahti Date: Sun, 15 Mar 2020 22:33:15 +0100 Subject: [PATCH 2/3] Fix method naming.. --- miio/vacuum.py | 9 +++------ miio/viomivacuum.py | 2 +- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/miio/vacuum.py b/miio/vacuum.py index 91412872b..ffeb59c4f 100644 --- a/miio/vacuum.py +++ b/miio/vacuum.py @@ -470,8 +470,8 @@ def _autodetect_model(self): ) @command() - def supported_fanspeeds(self) -> Dict[str, int]: - """Return dictionary containing supported fanspeeds.""" + def fan_speed_presets(self) -> Dict[str, int]: + """Return dictionary containing supported fan speeds.""" if self.model is None: self._autodetect_model() @@ -680,10 +680,7 @@ def cleanup(vac: Vacuum, *args, **kwargs): _LOGGER.debug("Writing %s to %s", seqs, id_file) path_obj = pathlib.Path(id_file) cache_dir = path_obj.parents[0] - try: - cache_dir.mkdir(parents=True) - except FileExistsError: - pass # after dropping py3.4 support, use exist_ok for mkdir + cache_dir.mkdir(parents=True, exist_ok=True) with open(id_file, "w") as f: json.dump(seqs, f) diff --git a/miio/viomivacuum.py b/miio/viomivacuum.py index 75ea871aa..a49588a03 100644 --- a/miio/viomivacuum.py +++ b/miio/viomivacuum.py @@ -341,6 +341,6 @@ def carpet_mode(self, mode: ViomiCarpetTurbo): return self.send("set_carpetturbo", [mode.value]) @command() - def supported_fanspeeds(self) -> Dict[str, int]: + def fan_speed_presets(self) -> Dict[str, int]: """Return dictionary containing supported fanspeeds.""" return {x.name: x.value for x in list(ViomiVacuumSpeed)} From b7b91131652aafe00ba606e192d2b49445e131d7 Mon Sep 17 00:00:00 2001 From: Teemu Rytilahti Date: Mon, 16 Mar 2020 21:50:32 +0100 Subject: [PATCH 3/3] small pre-merge cleanups --- miio/vacuum.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/miio/vacuum.py b/miio/vacuum.py index ffeb59c4f..8fd91cbf0 100644 --- a/miio/vacuum.py +++ b/miio/vacuum.py @@ -441,11 +441,11 @@ def _autodetect_model(self): For the moment this is used only for the fanspeeds, but that could be extended to cover other supported features.""" - # cloud-blocked vacuums will not return proper payloads try: info = self.info() self.model = info.model except TypeError: + # cloud-blocked vacuums will not return proper payloads self._fanspeeds = FanspeedV1 self.model = ROCKROBO_V1 _LOGGER.debug("Unable to query model, falling back to %s", self._fanspeeds) @@ -454,7 +454,7 @@ def _autodetect_model(self): _LOGGER.info("model: %s", self.model) if info.model == ROCKROBO_V1: - _LOGGER.info("Got robov1, checking for firmware version") + _LOGGER.debug("Got robov1, checking for firmware version") fw_version = info.firmware_version version, build = fw_version.split("_") version = tuple(map(int, version.split(".")))