From 4d51aed040046a902b2f905d775f4bda5868a3b7 Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Wed, 9 Nov 2022 19:37:02 +0100 Subject: [PATCH 01/11] Split out sensors --- miio/integrations/vacuum/roborock/vacuum.py | 121 +------- .../vacuum/roborock/vacuum_enums.py | 110 +++++++ .../vacuum/roborock/vacuumcontainers.py | 271 ++++++++++++++++-- 3 files changed, 362 insertions(+), 140 deletions(-) create mode 100644 miio/integrations/vacuum/roborock/vacuum_enums.py diff --git a/miio/integrations/vacuum/roborock/vacuum.py b/miio/integrations/vacuum/roborock/vacuum.py index 3f45dfac2..9ae6b5509 100644 --- a/miio/integrations/vacuum/roborock/vacuum.py +++ b/miio/integrations/vacuum/roborock/vacuum.py @@ -7,7 +7,7 @@ import os import pathlib import time -from typing import List, Optional, Type, Union +from typing import Dict, List, Optional, Tuple, Type, Union import click import pytz @@ -24,6 +24,7 @@ from miio.exceptions import DeviceInfoUnavailableException, UnsupportedFeatureException from miio.interfaces import FanspeedPresets, VacuumInterface +from .vacuum_enums import * from .vacuumcontainers import ( CarpetModeStatus, CleaningDetails, @@ -39,112 +40,6 @@ _LOGGER = logging.getLogger(__name__) -class TimerState(enum.Enum): - On = "on" - Off = "off" - - -class Consumable(enum.Enum): - MainBrush = "main_brush_work_time" - SideBrush = "side_brush_work_time" - Filter = "filter_work_time" - SensorDirty = "sensor_dirty_time" - - -class FanspeedEnum(enum.Enum): - pass - - -class FanspeedV1(FanspeedEnum): - Silent = 38 - Standard = 60 - Medium = 77 - Turbo = 90 - - -class FanspeedV2(FanspeedEnum): - Silent = 101 - Standard = 102 - Medium = 103 - Turbo = 104 - Gentle = 105 - Auto = 106 - - -class FanspeedV3(FanspeedEnum): - Silent = 38 - Standard = 60 - Medium = 75 - Turbo = 100 - - -class FanspeedE2(FanspeedEnum): - # Original names from the app: Gentle, Silent, Standard, Strong, Max - Gentle = 41 - Silent = 50 - Standard = 68 - Medium = 79 - Turbo = 100 - - -class FanspeedS7(FanspeedEnum): - Silent = 101 - Standard = 102 - Medium = 103 - Turbo = 104 - - -class FanspeedS7_Maxv(FanspeedEnum): - Silent = 101 - Standard = 102 - Medium = 103 - Turbo = 104 - Max = 108 - - -class WaterFlow(enum.Enum): - """Water flow strength on s5 max.""" - - Minimum = 200 - Low = 201 - High = 202 - Maximum = 203 - - -class MopMode(enum.Enum): - """Mop routing on S7.""" - - Standard = 300 - Deep = 301 - - -class MopIntensity(enum.Enum): - """Mop scrub intensity on S7 + S7MAXV.""" - - Close = 200 - Mild = 201 - Moderate = 202 - Intense = 203 - - -class CarpetCleaningMode(enum.Enum): - """Type of carpet cleaning/avoidance.""" - - Avoid = 0 - Rise = 1 - Ignore = 2 - - -class DustCollectionMode(enum.Enum): - """Auto emptying mode (S7 + S7MAXV only)""" - - Smart = 0 - Quick = 1 - Daily = 2 - Strong = 3 - Max = 4 - - ROCKROBO_V1 = "rockrobo.vacuum.v1" ROCKROBO_S4 = "roborock.vacuum.s4" ROCKROBO_S4_MAX = "roborock.vacuum.a19" @@ -410,11 +305,17 @@ def manual_control( @command() def status(self) -> VacuumStatus: """Return status of the vacuum.""" - status = VacuumStatus(self.send("get_status")[0]) + status = self.vacuum_status() status.embed(self.consumable_status()) status.embed(self.clean_history()) + status.embed(self.dnd_status()) return status + @command() + def vacuum_status(self) -> VacuumStatus: + """Return only status of the vacuum.""" + return VacuumStatus(self.send("get_status")[0], self.get_multi_maps()) + def enable_log_upload(self): raise NotImplementedError("unknown parameters") # return self.send("enable_log_upload") @@ -964,7 +865,7 @@ def set_mop_mode(self, mop_mode: MopMode): @command() def mop_intensity(self) -> MopIntensity: """Get mop scrub intensity setting.""" - if self.model != ROCKROBO_S7: + if self.model not in [ROCKROBO_S7, ROCKROBO_S7_MAXV]: raise UnsupportedFeatureException( "Mop scrub intensity not supported by %s", self.model ) @@ -974,7 +875,7 @@ def mop_intensity(self) -> MopIntensity: @command(click.argument("mop_intensity", type=EnumType(MopIntensity))) def set_mop_intensity(self, mop_intensity: MopIntensity): """Set mop scrub intensity setting.""" - if self.model != ROCKROBO_S7: + if self.model not in [ROCKROBO_S7, ROCKROBO_S7_MAXV]: raise UnsupportedFeatureException( "Mop scrub intensity not supported by %s", self.model ) diff --git a/miio/integrations/vacuum/roborock/vacuum_enums.py b/miio/integrations/vacuum/roborock/vacuum_enums.py new file mode 100644 index 000000000..3cf0cab94 --- /dev/null +++ b/miio/integrations/vacuum/roborock/vacuum_enums.py @@ -0,0 +1,110 @@ +import enum + + +class TimerState(enum.Enum): + On = "on" + Off = "off" + + +class Consumable(enum.Enum): + MainBrush = "main_brush_work_time" + SideBrush = "side_brush_work_time" + Filter = "filter_work_time" + SensorDirty = "sensor_dirty_time" + + +class FanspeedEnum(enum.Enum): + pass + + +class FanspeedV1(FanspeedEnum): + Silent = 38 + Standard = 60 + Medium = 77 + Turbo = 90 + + +class FanspeedV2(FanspeedEnum): + Silent = 101 + Standard = 102 + Medium = 103 + Turbo = 104 + Gentle = 105 + Auto = 106 + + +class FanspeedV3(FanspeedEnum): + Silent = 38 + Standard = 60 + Medium = 75 + Turbo = 100 + + +class FanspeedE2(FanspeedEnum): + # Original names from the app: Gentle, Silent, Standard, Strong, Max + Gentle = 41 + Silent = 50 + Standard = 68 + Medium = 79 + Turbo = 100 + + +class FanspeedS7(FanspeedEnum): + Silent = 101 + Standard = 102 + Medium = 103 + Turbo = 104 + + +class FanspeedS7_Maxv(FanspeedEnum): + # Original names from the app: Quiet, Balanced, Turbo, Max, Max+ + Off = 105 + Silent = 101 + Standard = 102 + Medium = 103 + Turbo = 104 + Max = 108 + + +class WaterFlow(enum.Enum): + """Water flow strength on s5 max.""" + + Minimum = 200 + Low = 201 + High = 202 + Maximum = 203 + + +class MopMode(enum.Enum): + """Mop routing on S7 + S7MAXV.""" + + Standard = 300 + Deep = 301 + DeepPlus = 303 + + +class MopIntensity(enum.Enum): + """Mop scrub intensity on S7 + S7MAXV.""" + + Off = 200 + Mild = 201 + Moderate = 202 + Intense = 203 + + +class CarpetCleaningMode(enum.Enum): + """Type of carpet cleaning/avoidance.""" + + Avoid = 0 + Rise = 1 + Ignore = 2 + + +class DustCollectionMode(enum.Enum): + """Auto emptying mode (S7 + S7MAXV only)""" + + Smart = 0 + Quick = 1 + Daily = 2 + Strong = 3 + Max = 4 diff --git a/miio/integrations/vacuum/roborock/vacuumcontainers.py b/miio/integrations/vacuum/roborock/vacuumcontainers.py index 6e62e3fb0..dea03fbc5 100644 --- a/miio/integrations/vacuum/roborock/vacuumcontainers.py +++ b/miio/integrations/vacuum/roborock/vacuumcontainers.py @@ -5,11 +5,14 @@ from croniter import croniter from pytz import BaseTzInfo +from miio.descriptors import SensorDescriptor from miio.device import DeviceStatus -from miio.devicestatus import sensor, setting +from miio.devicestatus import sensor, setting, switch from miio.interfaces.vacuuminterface import VacuumDeviceStatus, VacuumState from miio.utils import pretty_seconds, pretty_time +from .vacuum_enums import MopIntensity, MopMode + def pretty_area(x: float) -> float: return int(x) / 1000000 @@ -80,6 +83,15 @@ def pretty_area(x: float) -> float: 22: "Clean the dock charging contacts", 23: "Docking station not reachable", 24: "No-go zone or invisible wall detected", + 26: "Wall sensor is dirty", + 27: "VibraRise system is jammed", + 28: "Roborock is on carpet", +} + +dock_error_codes = { # from vacuum_cleaner-EN.pdf + 0: "No error", + 38: "Clean water tank empty", + 39: "Dirty water tank full", } @@ -129,13 +141,13 @@ def __init__(self, data: Dict[str, Any]) -> None: self.data = data @property - @sensor("State Code") + @sensor("State code", entity_category="diagnostic", enabled_default=False) def state_code(self) -> int: """State code as returned by the device.""" return int(self.data["state"]) @property - @sensor("State message") + @sensor("State", entity_category="diagnostic") def state(self) -> str: """Human readable state description, see also :func:`state_code`.""" return STATE_CODE_TO_STRING.get( @@ -148,13 +160,23 @@ def vacuum_state(self) -> VacuumState: return STATE_CODE_TO_VACUUMSTATE.get(self.state_code, VacuumState.Unknown) @property - @sensor("Error Code", icon="mdi:alert") + @sensor( + "Error code", + icon="mdi:alert", + entity_category="diagnostic", + enabled_default=False, + ) def error_code(self) -> int: """Error code as returned by the device.""" return int(self.data["error_code"]) @property - @sensor("Error", icon="mdi:alert") + @sensor( + "Error string", + icon="mdi:alert", + entity_category="diagnostic", + enabled_default=False, + ) def error(self) -> str: """Human readable error description, see also :func:`error_code`.""" try: @@ -163,33 +185,76 @@ def error(self) -> str: return "Definition missing for error %s" % self.error_code @property - @sensor("Battery", unit="%", device_class="battery") + @sensor( + "Dock error code", + icon="mdi:alert", + entity_category="diagnostic", + enabled_default=False, + ) + def dock_error_code(self) -> int: + """Dock error status as returned by the device.""" + return int(self.data["dock_error_status"]) + + @property + @sensor( + "Dock error string", + icon="mdi:alert", + entity_category="diagnostic", + enabled_default=False, + ) + def dock_error(self) -> str: + """Human readable dock error description, see also :func:`dock_error_code`.""" + try: + return dock_error_codes[self.dock_error_code] + except KeyError: + return "Definition missing for dock error %s" % self.dock_error_code + + @property + @sensor("Battery", unit="%", device_class="battery", enabled_default=False) def battery(self) -> int: """Remaining battery in percentage.""" return int(self.data["battery"]) @property - @setting( - "Fanspeed", - unit="%", - setter_name="set_fan_speed", - min_value=0, - max_value=100, - step=1, - icon="mdi:fan", - ) def fanspeed(self) -> int: """Current fan speed.""" return int(self.data["fan_power"]) @property - @sensor("Clean Duration", unit="s", icon="mdi:timer-sand") + @setting( + "Mop scrub intensity", + choices=MopIntensity, + setter_name="set_mop_intensity", + icon="mdi:checkbox-multiple-blank-circle-outline", + ) + def mop_intensity(self) -> int: + """Current mop intensity.""" + return int(self.data["water_box_mode"]) + + @property + @setting( + "Mop route", + choices=MopMode, + setter_name="set_mop_mode", + icon="mdi:swap-horizontal-variant", + ) + def mop_route(self) -> int: + """Current mop route.""" + return int(self.data["mop_mode"]) + + @property + @sensor( + "Current clean duration", + unit="s", + icon="mdi:timer-sand", + device_class="duration", + ) def clean_time(self) -> timedelta: """Time used for cleaning (if finished, shows how long it took).""" return pretty_seconds(self.data["clean_time"]) @property - @sensor("Cleaned Area", unit="m2", icon="mdi:texture-box") + @sensor("Current clean area", unit="m²", icon="mdi:texture-box") def clean_area(self) -> float: """Cleaned area in m2.""" return pretty_area(self.data["clean_area"]) @@ -226,7 +291,7 @@ def is_on(self) -> bool: ) @property - @sensor("Water Box Attached") + @sensor("Water box attached", icon="mdi:cup-water") def is_water_box_attached(self) -> Optional[bool]: """Return True is water box is installed.""" if "water_box_status" in self.data: @@ -234,7 +299,7 @@ def is_water_box_attached(self) -> Optional[bool]: return None @property - @sensor("Mop Attached") + @sensor("Mop attached") def is_water_box_carriage_attached(self) -> Optional[bool]: """Return True if water box carriage (mop) is installed, None if sensor not present.""" @@ -243,7 +308,7 @@ def is_water_box_carriage_attached(self) -> Optional[bool]: return None @property - @sensor("Water Level Low", icon="mdi:alert") + @sensor("Water level low", icon="mdi:water-alert-outline") def is_water_shortage(self) -> Optional[bool]: """Returns True if water is low in the tank, None if sensor not present.""" if "water_shortage_status" in self.data: @@ -251,7 +316,23 @@ def is_water_shortage(self) -> Optional[bool]: return None @property - @sensor("Error", icon="mdi:alert") + @switch( + "Auto dust collection", + setter_name="set_dust_collection", + icon="mdi:turbine", + entity_category="config", + ) + def auto_dust_collection(self) -> Optional[bool]: + """Returns True if auto dust collection is enabled, None if sensor not + present.""" + if "auto_dust_collection" in self.data: + return self.data["auto_dust_collection"] == 1 + return None + + @property + @sensor( + "Error", icon="mdi:alert", entity_category="diagnostic", enabled_default=False + ) def got_error(self) -> bool: """True if an error has occurred.""" return self.error_code != 0 @@ -283,30 +364,52 @@ def __init__(self, data: Union[List[Any], Dict[str, Any]]) -> None: self.data["records"] = [] @property - @sensor("Total Cleaning Time", icon="mdi:timer-sand") + @sensor( + "Total clean duration", + unit="s", + icon="mdi:timer-sand", + device_class="duration", + entity_category="diagnostic", + ) def total_duration(self) -> timedelta: """Total cleaning duration.""" return pretty_seconds(self.data["clean_time"]) @property - @sensor("Total Cleaning Area", icon="mdi:texture-box") + @sensor( + "Total clean area", + unit="m²", + icon="mdi:texture-box", + entity_category="diagnostic", + ) def total_area(self) -> float: """Total cleaned area.""" return pretty_area(self.data["clean_area"]) @property - @sensor("Total Clean Count") + @sensor( + "Total clean count", + icon="mdi:counter", + state_class="total_increasing", + entity_category="diagnostic", + ) def count(self) -> int: """Number of cleaning runs.""" return int(self.data["clean_count"]) @property def ids(self) -> List[int]: - """A list of available cleaning IDs, see also :class:`CleaningDetails`.""" + """A list of available cleaning IDs, see also + :class:`CleaningDetails`.""" return list(self.data["records"]) @property - @sensor("Dust Collection Count") + @sensor( + "Total dust collection count", + icon="mdi:counter", + state_class="total_increasing", + entity_category="diagnostic", + ) def dust_collection_count(self) -> Optional[int]: """Total number of dust collections.""" if "dust_collection_count" in self.data: @@ -335,21 +438,46 @@ def __init__(self, data: Union[List[Any], Dict[str, Any]]) -> None: self.data = data @property + @sensor( + "Last clean start", + icon="mdi:clock-time-twelve", + device_class="timestamp", + entity_category="diagnostic", + ) def start(self) -> datetime: """When cleaning was started.""" return pretty_time(self.data["begin"]) @property + @sensor( + "Last clean end", + icon="mdi:clock-time-twelve", + device_class="timestamp", + entity_category="diagnostic", + ) def end(self) -> datetime: """When cleaning was finished.""" return pretty_time(self.data["end"]) @property + @sensor( + "Last clean duration", + unit="s", + icon="mdi:timer-sand", + device_class="duration", + entity_category="diagnostic", + ) def duration(self) -> timedelta: """Total duration of the cleaning run.""" return pretty_seconds(self.data["duration"]) @property + @sensor( + "Last clean area", + unit="m²", + icon="mdi:texture-box", + entity_category="diagnostic", + ) def area(self) -> float: """Total cleaned area.""" return pretty_area(self.data["area"]) @@ -397,46 +525,115 @@ def __init__(self, data: Dict[str, Any]) -> None: self.sensor_dirty_total = timedelta(hours=30) @property - @sensor("Main Brush Usage", unit="s") + @sensor( + "Main brush used", + unit="s", + icon="mdi:brush", + device_class="duration", + entity_category="diagnostic", + enabled_default=False, + ) def main_brush(self) -> timedelta: """Main brush usage time.""" return pretty_seconds(self.data["main_brush_work_time"]) @property - @sensor("Main Brush Remaining", unit="s") + @sensor( + "Main brush left", + unit="s", + icon="mdi:brush", + device_class="duration", + entity_category="diagnostic", + ) def main_brush_left(self) -> timedelta: """How long until the main brush should be changed.""" return self.main_brush_total - self.main_brush @property + @sensor( + "Side brush used", + unit="s", + icon="mdi:brush", + device_class="duration", + entity_category="diagnostic", + enabled_default=False, + ) def side_brush(self) -> timedelta: """Side brush usage time.""" return pretty_seconds(self.data["side_brush_work_time"]) @property + @sensor( + "Side brush left", + unit="s", + icon="mdi:brush", + device_class="duration", + entity_category="diagnostic", + ) def side_brush_left(self) -> timedelta: """How long until the side brush should be changed.""" return self.side_brush_total - self.side_brush @property + @sensor( + "Filter used", + unit="s", + icon="mdi:air-filter", + device_class="duration", + entity_category="diagnostic", + enabled_default=False, + ) def filter(self) -> timedelta: """Filter usage time.""" return pretty_seconds(self.data["filter_work_time"]) @property + @sensor( + "Filter left", + unit="s", + icon="mdi:air-filter", + device_class="duration", + entity_category="diagnostic", + ) def filter_left(self) -> timedelta: """How long until the filter should be changed.""" return self.filter_total - self.filter @property + @sensor( + "Sensor dirty used", + unit="s", + icon="mdi:eye-outline", + device_class="duration", + entity_category="diagnostic", + enabled_default=False, + ) def sensor_dirty(self) -> timedelta: """Return ``sensor_dirty_time``""" return pretty_seconds(self.data["sensor_dirty_time"]) @property + @sensor( + "Sensor dirty left", + unit="s", + icon="mdi:eye-outline", + device_class="duration", + entity_category="diagnostic", + ) def sensor_dirty_left(self) -> timedelta: return self.sensor_dirty_total - self.sensor_dirty + @property + @sensor( + "Dustbin times auto-empty used", + icon="mdi:delete", + entity_category="diagnostic", + enabled_default=False, + ) + def dustbin_auto_empty_used(self) -> int: + """Return ``dust_collection_work_times``""" + return self.data["dust_collection_work_times"] + class DNDStatus(DeviceStatus): """A container for the do-not-disturb status.""" @@ -447,17 +644,31 @@ def __init__(self, data: Dict[str, Any]): self.data = data @property - @sensor("Do Not Disturb") + @sensor("Do not disturb", icon="mdi:minus-circle-off", entity_category="diagnostic") def enabled(self) -> bool: """True if DnD is enabled.""" return bool(self.data["enabled"]) @property + @sensor( + "Do not disturb start", + icon="mdi:minus-circle-off", + device_class="timestamp", + entity_category="diagnostic", + enabled_default=False, + ) def start(self) -> time: """Start time of DnD.""" return time(hour=self.data["start_hour"], minute=self.data["start_minute"]) @property + @sensor( + "Do not disturb end", + icon="mdi:minus-circle-off", + device_class="timestamp", + entity_category="diagnostic", + enabled_default=False, + ) def end(self) -> time: """End time of DnD.""" return time(hour=self.data["end_hour"], minute=self.data["end_minute"]) @@ -616,7 +827,7 @@ def __init__(self, data): self.data = data @property - @sensor("Carpet Mode") + @sensor("Carpet mode") def enabled(self) -> bool: """True if carpet mode is enabled.""" return self.data["enable"] == 1 From 7a84060e9c9fe7e0167b56947d8b91d87afcde4c Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Wed, 9 Nov 2022 19:47:49 +0100 Subject: [PATCH 02/11] fix imports --- miio/integrations/vacuum/roborock/vacuum.py | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/miio/integrations/vacuum/roborock/vacuum.py b/miio/integrations/vacuum/roborock/vacuum.py index 9ae6b5509..30208c6ad 100644 --- a/miio/integrations/vacuum/roborock/vacuum.py +++ b/miio/integrations/vacuum/roborock/vacuum.py @@ -1,13 +1,12 @@ import contextlib import datetime -import enum import json import logging import math import os import pathlib import time -from typing import Dict, List, Optional, Tuple, Type, Union +from typing import List, Optional, Type, Union import click import pytz @@ -24,7 +23,22 @@ from miio.exceptions import DeviceInfoUnavailableException, UnsupportedFeatureException from miio.interfaces import FanspeedPresets, VacuumInterface -from .vacuum_enums import * +from .vacuum_enums import ( + CarpetCleaningMode, + Consumable, + DustCollectionMode, + FanspeedE2, + FanspeedEnum, + FanspeedS7, + FanspeedS7_Maxv, + FanspeedV1, + FanspeedV2, + FanspeedV3, + MopIntensity, + MopMode, + TimerState, + WaterFlow, +) from .vacuumcontainers import ( CarpetModeStatus, CleaningDetails, From d29ca91d84588d360e22585d95f4d44bf93ae33b Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Wed, 9 Nov 2022 19:48:27 +0100 Subject: [PATCH 03/11] fix imports --- miio/integrations/vacuum/roborock/vacuumcontainers.py | 1 - 1 file changed, 1 deletion(-) diff --git a/miio/integrations/vacuum/roborock/vacuumcontainers.py b/miio/integrations/vacuum/roborock/vacuumcontainers.py index dea03fbc5..608fd4ad3 100644 --- a/miio/integrations/vacuum/roborock/vacuumcontainers.py +++ b/miio/integrations/vacuum/roborock/vacuumcontainers.py @@ -5,7 +5,6 @@ from croniter import croniter from pytz import BaseTzInfo -from miio.descriptors import SensorDescriptor from miio.device import DeviceStatus from miio.devicestatus import sensor, setting, switch from miio.interfaces.vacuuminterface import VacuumDeviceStatus, VacuumState From 5b993c0f062e563b11321921745d5adacc2b3b29 Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Wed, 9 Nov 2022 19:57:58 +0100 Subject: [PATCH 04/11] use boolean setting --- miio/integrations/vacuum/roborock/vacuumcontainers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/miio/integrations/vacuum/roborock/vacuumcontainers.py b/miio/integrations/vacuum/roborock/vacuumcontainers.py index 608fd4ad3..c3230cc97 100644 --- a/miio/integrations/vacuum/roborock/vacuumcontainers.py +++ b/miio/integrations/vacuum/roborock/vacuumcontainers.py @@ -6,7 +6,7 @@ from pytz import BaseTzInfo from miio.device import DeviceStatus -from miio.devicestatus import sensor, setting, switch +from miio.devicestatus import sensor, setting from miio.interfaces.vacuuminterface import VacuumDeviceStatus, VacuumState from miio.utils import pretty_seconds, pretty_time @@ -315,7 +315,7 @@ def is_water_shortage(self) -> Optional[bool]: return None @property - @switch( + @setting( "Auto dust collection", setter_name="set_dust_collection", icon="mdi:turbine", From aea4b528488d25eec34efd14c12056d56e26b6af Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Wed, 9 Nov 2022 20:22:07 +0100 Subject: [PATCH 05/11] fix --- miio/integrations/vacuum/roborock/vacuum.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/miio/integrations/vacuum/roborock/vacuum.py b/miio/integrations/vacuum/roborock/vacuum.py index 30208c6ad..1e0cd5272 100644 --- a/miio/integrations/vacuum/roborock/vacuum.py +++ b/miio/integrations/vacuum/roborock/vacuum.py @@ -328,7 +328,7 @@ def status(self) -> VacuumStatus: @command() def vacuum_status(self) -> VacuumStatus: """Return only status of the vacuum.""" - return VacuumStatus(self.send("get_status")[0], self.get_multi_maps()) + return VacuumStatus(self.send("get_status")[0]) def enable_log_upload(self): raise NotImplementedError("unknown parameters") From 0317b0cc0419a260caa49a5638ca0772f38294d4 Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Wed, 9 Nov 2022 20:46:16 +0100 Subject: [PATCH 06/11] Update test_vacuum.py --- .../integrations/vacuum/roborock/tests/test_vacuum.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/miio/integrations/vacuum/roborock/tests/test_vacuum.py b/miio/integrations/vacuum/roborock/tests/test_vacuum.py index af9408bd0..f88df3e4f 100644 --- a/miio/integrations/vacuum/roborock/tests/test_vacuum.py +++ b/miio/integrations/vacuum/roborock/tests/test_vacuum.py @@ -62,6 +62,15 @@ def __init__(self, *args, **kwargs): 1487548800, ], ] + self.dummies["dnd_timer"] = [ + { + "enabled": 1, + "start_minute": 0, + "end_minute": 0, + "start_hour": 22, + "end_hour": 8, + } + ] self.return_values = { "get_status": lambda x: [self.state], @@ -75,6 +84,8 @@ def __init__(self, *args, **kwargs): "app_zoned_clean": lambda x: self.change_mode("zoned clean"), "app_charge": lambda x: self.change_mode("charge"), "miIO.info": "dummy info", + "get_clean_record": lambda x: [[1488347071, 1488347123, 16, 0, 0, 0]], + "get_dnd_timer": lambda x: self.dummies["dnd_timer"], } super().__init__(args, kwargs) From 63d6921232522e3e65c92f053d18c90b4adcbfda Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Wed, 9 Nov 2022 21:30:39 +0100 Subject: [PATCH 07/11] restore Fanspeed --- .../vacuum/roborock/vacuumcontainers.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/miio/integrations/vacuum/roborock/vacuumcontainers.py b/miio/integrations/vacuum/roborock/vacuumcontainers.py index c3230cc97..1e87566f7 100644 --- a/miio/integrations/vacuum/roborock/vacuumcontainers.py +++ b/miio/integrations/vacuum/roborock/vacuumcontainers.py @@ -215,9 +215,21 @@ def battery(self) -> int: return int(self.data["battery"]) @property + @setting( + "Fanspeed", + unit="%", + setter_name="set_fan_speed", + min_value=0, + max_value=100, + step=1, + icon="mdi:fan", + ) def fanspeed(self) -> int: """Current fan speed.""" - return int(self.data["fan_power"]) + fan_power = int(self.data["fan_power"]) + if fan_power > 100: + return None + return fan_power @property @setting( From c73a5f83538e0f635fdf64d5af8d752e242970e6 Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Wed, 9 Nov 2022 21:36:18 +0100 Subject: [PATCH 08/11] add commment --- miio/integrations/vacuum/roborock/vacuumcontainers.py | 1 + 1 file changed, 1 insertion(+) diff --git a/miio/integrations/vacuum/roborock/vacuumcontainers.py b/miio/integrations/vacuum/roborock/vacuumcontainers.py index 1e87566f7..f2adfb7e1 100644 --- a/miio/integrations/vacuum/roborock/vacuumcontainers.py +++ b/miio/integrations/vacuum/roborock/vacuumcontainers.py @@ -228,6 +228,7 @@ def fanspeed(self) -> int: """Current fan speed.""" fan_power = int(self.data["fan_power"]) if fan_power > 100: + # values 100+ are reserved for presets return None return fan_power From 187002da087b2814ea8dc39dde0845a06508c08c Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Wed, 9 Nov 2022 21:36:47 +0100 Subject: [PATCH 09/11] fix type --- miio/integrations/vacuum/roborock/vacuumcontainers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/miio/integrations/vacuum/roborock/vacuumcontainers.py b/miio/integrations/vacuum/roborock/vacuumcontainers.py index f2adfb7e1..e01b7fcaf 100644 --- a/miio/integrations/vacuum/roborock/vacuumcontainers.py +++ b/miio/integrations/vacuum/roborock/vacuumcontainers.py @@ -224,7 +224,7 @@ def battery(self) -> int: step=1, icon="mdi:fan", ) - def fanspeed(self) -> int: + def fanspeed(self) -> Optional[int]: """Current fan speed.""" fan_power = int(self.data["fan_power"]) if fan_power > 100: From 1298a9f83aa1b7b736b484e6225b74a360c22f3b Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Wed, 9 Nov 2022 22:19:58 +0100 Subject: [PATCH 10/11] Check for keyerrors --- .../vacuum/roborock/vacuumcontainers.py | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/miio/integrations/vacuum/roborock/vacuumcontainers.py b/miio/integrations/vacuum/roborock/vacuumcontainers.py index e01b7fcaf..03eeab85a 100644 --- a/miio/integrations/vacuum/roborock/vacuumcontainers.py +++ b/miio/integrations/vacuum/roborock/vacuumcontainers.py @@ -190,9 +190,11 @@ def error(self) -> str: entity_category="diagnostic", enabled_default=False, ) - def dock_error_code(self) -> int: + def dock_error_code(self) -> Optional[int]: """Dock error status as returned by the device.""" - return int(self.data["dock_error_status"]) + if "dock_error_status" in self.data: + return int(self.data["dock_error_status"]) + return None @property @sensor( @@ -203,6 +205,8 @@ def dock_error_code(self) -> int: ) def dock_error(self) -> str: """Human readable dock error description, see also :func:`dock_error_code`.""" + if self.dock_error_code is None: + return None try: return dock_error_codes[self.dock_error_code] except KeyError: @@ -239,9 +243,11 @@ def fanspeed(self) -> Optional[int]: setter_name="set_mop_intensity", icon="mdi:checkbox-multiple-blank-circle-outline", ) - def mop_intensity(self) -> int: + def mop_intensity(self) -> Optional[int]: """Current mop intensity.""" - return int(self.data["water_box_mode"]) + if "water_box_mode" in self.data: + return int(self.data["water_box_mode"]) + return None @property @setting( @@ -250,9 +256,11 @@ def mop_intensity(self) -> int: setter_name="set_mop_mode", icon="mdi:swap-horizontal-variant", ) - def mop_route(self) -> int: + def mop_route(self) -> Optional[int]: """Current mop route.""" - return int(self.data["mop_mode"]) + if "mop_mode" in self.data: + return int(self.data["mop_mode"]) + return None @property @sensor( @@ -642,9 +650,11 @@ def sensor_dirty_left(self) -> timedelta: entity_category="diagnostic", enabled_default=False, ) - def dustbin_auto_empty_used(self) -> int: + def dustbin_auto_empty_used(self) -> Optional[int]: """Return ``dust_collection_work_times``""" - return self.data["dust_collection_work_times"] + if "dust_collection_work_times" in self.data: + return self.data["dust_collection_work_times"] + return None class DNDStatus(DeviceStatus): From c7fff709120c09fbff40d5f761016007e4fae3fb Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Wed, 9 Nov 2022 22:26:32 +0100 Subject: [PATCH 11/11] typing --- miio/integrations/vacuum/roborock/vacuumcontainers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/miio/integrations/vacuum/roborock/vacuumcontainers.py b/miio/integrations/vacuum/roborock/vacuumcontainers.py index 03eeab85a..a7d81a92b 100644 --- a/miio/integrations/vacuum/roborock/vacuumcontainers.py +++ b/miio/integrations/vacuum/roborock/vacuumcontainers.py @@ -203,7 +203,7 @@ def dock_error_code(self) -> Optional[int]: entity_category="diagnostic", enabled_default=False, ) - def dock_error(self) -> str: + def dock_error(self) -> Optional[str]: """Human readable dock error description, see also :func:`dock_error_code`.""" if self.dock_error_code is None: return None