• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

rytilahti / python-miio / 1970

pending completion
1970

push

travis-ci

web-flow
Add mopping state & log a warning when encountering unknown state (#784)

586 of 1011 branches covered (57.96%)

Branch coverage included in aggregate %.

9 of 9 new or added lines in 1 file covered. (100.0%)

4681 of 6108 relevant lines covered (76.64%)

1.53 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

67.84
/miio/viomivacuum.py
1
import logging
2✔
2
import time
2✔
3
from collections import defaultdict
2✔
4
from datetime import timedelta
2✔
5
from enum import Enum
2✔
6
from typing import Dict, Optional
2✔
7

8
import click
2✔
9

10
from .click_common import EnumType, command, format_output
2✔
11
from .device import Device
2✔
12
from .utils import pretty_seconds
2✔
13
from .vacuumcontainers import DNDStatus
2✔
14

15
_LOGGER = logging.getLogger(__name__)
2✔
16

17

18
ERROR_CODES = {
2✔
19
    500: "Radar timed out",
20
    501: "Wheels stuck",
21
    502: "Low battery",
22
    503: "Dust bin missing",
23
    508: "Uneven ground",
24
    509: "Cliff sensor error",
25
    510: "Collision sensor error",
26
    511: "Could not return to dock",
27
    512: "Could not return to dock",
28
    513: "Could not navigate",
29
    514: "Vacuum stuck",
30
    515: "Charging error",
31
    516: "Mop temperature error",
32
    521: "Water tank is not installed",
33
    522: "Mop is not installed",
34
    525: "Insufficient water in water tank",
35
    527: "Remove mop",
36
    528: "Dust bin missing",
37
    529: "Mop and water tank missing",
38
    530: "Mop and water tank missing",
39
    531: "Water tank is not installed",
40
    2101: "Unsufficient battery, continuing cleaning after recharge",
41
}
42

43

44
class ViomiVacuumSpeed(Enum):
2✔
45
    Silent = 0
2✔
46
    Standard = 1
2✔
47
    Medium = 2
2✔
48
    Turbo = 3
2✔
49

50

51
class ViomiVacuumState(Enum):
2✔
52
    Unknown = -1
2✔
53
    IdleNotDocked = 0
2✔
54
    Idle = 1
2✔
55
    Idle2 = 2
2✔
56
    Cleaning = 3
2✔
57
    Returning = 4
2✔
58
    Docked = 5
2✔
59
    Mopping = 6
2✔
60

61

62
class ViomiMode(Enum):
2✔
63
    Vacuum = 0  # No Mop, Vacuum only
2✔
64
    VacuumAndMop = 1
2✔
65
    Mop = 2
2✔
66

67

68
class ViomiLanguage(Enum):
2✔
69
    CN = 1  # Chinese (default)
2✔
70
    EN = 2  # English
2✔
71

72

73
class ViomiLedState(Enum):
2✔
74
    Off = 0
2✔
75
    On = 1
2✔
76

77

78
class ViomiCarpetTurbo(Enum):
2✔
79
    Off = 0
2✔
80
    Medium = 1
2✔
81
    Turbo = 2
2✔
82

83

84
class ViomiMovementDirection(Enum):
2✔
85
    Forward = 1
2✔
86
    Left = 2  # Rotate
2✔
87
    Right = 3  # Rotate
2✔
88
    Backward = 4
2✔
89
    Stop = 5
2✔
90
    Unknown = 10
2✔
91

92

93
class ViomiBinType(Enum):
2✔
94
    Vacuum = 1
2✔
95
    Water = 2
2✔
96
    VacuumAndWater = 3
2✔
97

98

99
class ViomiWaterGrade(Enum):
2✔
100
    Low = 11
2✔
101
    Medium = 12
2✔
102
    High = 13
2✔
103

104

105
class ViomiMopMode(Enum):
2✔
106
    """Mopping pattern."""
107

108
    S = 0
2✔
109
    Y = 1
2✔
110

111

112
class ViomiVacuumStatus:
2✔
113
    def __init__(self, data):
2✔
114
        # ["run_state","mode","err_state","battary_life","box_type","mop_type","s_time","s_area",
115
        # [ 5,          0,     2103,       85,            3,         1,         0,       0,
116
        # "suction_grade","water_grade","remember_map","has_map","is_mop","has_newmap"]'
117
        # 1,               11,           1,            1,         1,       0          ]
118
        self.data = data
×
119

120
    @property
2✔
121
    def state(self):
122
        """State of the vacuum."""
123
        try:
×
124
            return ViomiVacuumState(self.data["run_state"])
×
125
        except ValueError:
×
126
            _LOGGER.warning("Unknown vacuum state: %s", self.data["run_state"])
×
127
            return ViomiVacuumState.Unknown
×
128

129
    @property
2✔
130
    def is_on(self) -> bool:
2✔
131
        """True if cleaning."""
132
        cleaning_states = [ViomiVacuumState.Cleaning, ViomiVacuumState.Mopping]
×
133
        return self.state in cleaning_states
×
134

135
    @property
2✔
136
    def mode(self):
137
        """Active mode.
138

139
        TODO: is this same as mop_type property?
140
        """
141
        return ViomiMode(self.data["mode"])
×
142

143
    @property
2✔
144
    def mop_type(self):
145
        """Unknown mop_type values."""
146
        return self.data["mop_type"]
×
147

148
    @property
2✔
149
    def error_code(self) -> int:
2✔
150
        """Error code from vacuum."""
151
        return self.data["err_state"]
×
152

153
    @property
2✔
154
    def error(self) -> Optional[str]:
2✔
155
        """String presentation for the error code."""
156
        if self.error_code is None:
×
157
            return None
×
158

159
        return ERROR_CODES.get(self.error_code, f"Unknown error {self.error_code}")
×
160

161
    @property
2✔
162
    def battery(self) -> int:
2✔
163
        """Battery in percentage."""
164
        return self.data["battary_life"]
×
165

166
    @property
2✔
167
    def bin_type(self) -> ViomiBinType:
2✔
168
        """Type of the inserted bin."""
169
        return ViomiBinType(self.data["box_type"])
×
170

171
    @property
2✔
172
    def clean_time(self) -> timedelta:
2✔
173
        """Cleaning time."""
174
        return pretty_seconds(self.data["s_time"])
×
175

176
    @property
2✔
177
    def clean_area(self) -> float:
2✔
178
        """Cleaned area in square meters."""
179
        return self.data["s_area"]
×
180

181
    @property
2✔
182
    def fanspeed(self) -> ViomiVacuumSpeed:
2✔
183
        """Current fan speed."""
184
        return ViomiVacuumSpeed(self.data["suction_grade"])
×
185

186
    @property
2✔
187
    def water_grade(self) -> ViomiWaterGrade:
2✔
188
        """Water grade."""
189
        return ViomiWaterGrade(self.data["water_grade"])
×
190

191
    @property
2✔
192
    def remember_map(self) -> bool:
2✔
193
        """True to remember the map."""
194
        return bool(self.data["remember_map"])
×
195

196
    @property
2✔
197
    def has_map(self) -> bool:
2✔
198
        """True if device has map?"""
199
        return bool(self.data["has_map"])
×
200

201
    @property
2✔
202
    def has_new_map(self) -> bool:
2✔
203
        """TODO: unknown"""
204
        return bool(self.data["has_newmap"])
×
205

206
    @property
2✔
207
    def mop_mode(self) -> ViomiMode:
2✔
208
        """Whether mopping is enabled and if so which mode
209

210
        TODO: is this really the same as mode?
211
        """
212
        return ViomiMode(self.data["is_mop"])
×
213

214

215
class ViomiVacuum(Device):
2✔
216
    """Interface for Viomi vacuums (viomi.vacuum.v7)."""
217

218
    @command(
2✔
219
        default_output=format_output(
220
            "",
221
            "State: {result.state}\n"
222
            "Mode: {result.mode}\n"
223
            "Error: {result.error}\n"
224
            "Battery: {result.battery}\n"
225
            "Fan speed: {result.fanspeed}\n"
226
            "Box type: {result.bin_type}\n"
227
            "Mop type: {result.mop_type}\n"
228
            "Clean time: {result.clean_time}\n"
229
            "Clean area: {result.clean_area}\n"
230
            "Water grade: {result.water_grade}\n"
231
            "Remember map: {result.remember_map}\n"
232
            "Has map: {result.has_map}\n"
233
            "Has new map: {result.has_new_map}\n"
234
            "Mop mode: {result.mop_mode}\n",
235
        )
236
    )
237
    def status(self) -> ViomiVacuumStatus:
2✔
238
        """Retrieve properties."""
239
        properties = [
×
240
            "run_state",
241
            "mode",
242
            "err_state",
243
            "battary_life",
244
            "box_type",
245
            "mop_type",
246
            "s_time",
247
            "s_area",
248
            "suction_grade",
249
            "water_grade",
250
            "remember_map",
251
            "has_map",
252
            "is_mop",
253
            "has_newmap",
254
        ]
255

256
        values = self.get_properties(properties)
×
257

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

260
    @command()
2✔
261
    def start(self):
262
        """Start cleaning."""
263
        # TODO figure out the parameters
264
        self.send("set_mode_withroom", [0, 1, 0])
×
265

266
    @command()
2✔
267
    def stop(self):
268
        """Stop cleaning."""
269
        self.send("set_mode", [0])
×
270

271
    @command()
2✔
272
    def pause(self):
273
        """Pause cleaning."""
274
        self.send("set_mode_withroom", [0, 2, 0])
×
275

276
    @command(click.argument("speed", type=EnumType(ViomiVacuumSpeed, False)))
2✔
277
    def set_fan_speed(self, speed: ViomiVacuumSpeed):
2✔
278
        """Set fanspeed [silent, standard, medium, turbo]."""
279
        self.send("set_suction", [speed.value])
×
280

281
    @command(click.argument("watergrade"))
2✔
282
    def set_water_grade(self, watergrade: ViomiWaterGrade):
2✔
283
        """Set water grade [low, medium, high]."""
284
        self.send("set_suction", [watergrade.value])
×
285

286
    @command()
2✔
287
    def home(self):
288
        """Return to home."""
289
        self.send("set_charge", [1])
×
290

291
    @command(
2✔
292
        click.argument("direction", type=EnumType(ViomiMovementDirection, False)),
293
        click.option(
294
            "--duration",
295
            type=float,
296
            default=0.5,
297
            help="number of seconds to perform this movement",
298
        ),
299
    )
300
    def move(self, direction, duration=0.5):
2✔
301
        """Manual movement."""
302
        start = time.time()
×
303
        while time.time() - start < duration:
×
304
            self.send("set_direction", [direction.value])
×
305
            time.sleep(0.1)
×
306
        self.send("set_direction", [ViomiMovementDirection.Stop.value])
×
307

308
    @command(click.argument("mode", type=EnumType(ViomiMode, False)))
2✔
309
    def clean_mode(self, mode):
310
        """Set the cleaning mode."""
311
        self.send("set_mop", [mode.value])
×
312

313
    @command(click.argument("mop_mode", type=EnumType(ViomiMopMode, False)))
2✔
314
    def mop_mode(self, mop_mode):
315
        self.send("set_moproute", [mop_mode.value])
×
316

317
    @command()
2✔
318
    def dnd_status(self):
319
        """Returns do-not-disturb status."""
320
        status = self.send("get_notdisturb")
×
321
        return DNDStatus(
×
322
            dict(
323
                enabled=status[0],
324
                start_hour=status[1],
325
                start_minute=status[2],
326
                end_hour=status[3],
327
                end_minute=status[4],
328
            )
329
        )
330

331
    @command(
2✔
332
        click.option("--disable", is_flag=True),
333
        click.argument("start_hr", type=int),
334
        click.argument("start_min", type=int),
335
        click.argument("end_hr", type=int),
336
        click.argument("end_min", type=int),
337
    )
338
    def set_dnd(
2✔
339
        self, disable: bool, start_hr: int, start_min: int, end_hr: int, end_min: int
340
    ):
341
        """Set do-not-disturb.
342

343
        :param int start_hr: Start hour
344
        :param int start_min: Start minute
345
        :param int end_hr: End hour
346
        :param int end_min: End minute"""
347
        return self.send(
×
348
            "set_notdisturb",
349
            [0 if disable else 1, start_hr, start_min, end_hr, end_min],
350
        )
351

352
    @command(click.argument("language", type=EnumType(ViomiLanguage, False)))
2✔
353
    def set_language(self, language: ViomiLanguage):
2✔
354
        """Set the device's audio language."""
355
        return self.send("set_language", [language.value])
×
356

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

362
    @command(click.argument("mode", type=EnumType(ViomiCarpetTurbo)))
2✔
363
    def carpet_mode(self, mode: ViomiCarpetTurbo):
2✔
364
        """Set the carpet mode."""
365
        return self.send("set_carpetturbo", [mode.value])
×
366

367
    @command()
2✔
368
    def fan_speed_presets(self) -> Dict[str, int]:
2✔
369
        """Return dictionary containing supported fanspeeds."""
370
        return {x.name: x.value for x in list(ViomiVacuumSpeed)}
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc