From 63113b392f48d59e5ccde372778caf2f81619a21 Mon Sep 17 00:00:00 2001 From: Sebastian Muszynski Date: Sun, 26 Nov 2017 15:09:52 +0100 Subject: [PATCH 01/11] Some comments improved. Additional properties of the Xiaomi Air Purifier 2 introduced. Some code clean-up. --- miio/airpurifier.py | 92 ++++++++++++++++++++++++++----------- miio/ceil.py | 8 ++-- miio/fan.py | 7 +-- miio/philips_eyecare.py | 34 +++++++------- miio/philips_eyecare_cli.py | 10 ++-- 5 files changed, 95 insertions(+), 56 deletions(-) diff --git a/miio/airpurifier.py b/miio/airpurifier.py index aa9981283..345fa0f4b 100644 --- a/miio/airpurifier.py +++ b/miio/airpurifier.py @@ -1,5 +1,6 @@ import logging import enum +import math from typing import Any, Dict, Optional from collections import defaultdict from .device import Device @@ -36,14 +37,13 @@ def __init__(self, data: Dict[str, Any]) -> None: Response of a Air Purifier 2: - ['power': 'off', 'aqi': 141, 'humidity': 64, 'temp_dec': 236, - 'mode': 'auto', 'led': 'on', 'led_b': 1, 'buzzer': 'on', - 'child_lock': 'off', 'limit_hum': null, 'trans_level': null, - 'bright': null, 'favorite_level': 10, 'filter1_life': 80, - 'act_det': null, 'f1_hour_used': 680 ] + ['power': 'on', 'aqi': 22, 'humidity': 61, 'temp_dec': 197, + 'mode': 'auto', 'led': 2, 'led_b': 'off', 'buzzer': 'off', + 'child_lock': 'off', 'bright': None, 'favorite_level': 10, + 'filter1_life': 80, 'f1_hour_used': 678, 'use_time': 2440800, + 'motor1_speed': 351, 'purify_volume': 25127] - use_time and motor1_speed is missing because a request is limitted - to 16 properties. We request 15 properties at the moment. + A request is limitted to 16 properties. """ self.data = data @@ -63,6 +63,11 @@ def aqi(self) -> int: """Air quality index.""" return self.data["aqi"] + @property + def average_aqi(self) -> int: + """Average of the air quality index.""" + return self.data["averageaqi"] + @property def humidity(self) -> int: """Current humidity.""" @@ -90,6 +95,11 @@ def led_brightness(self) -> Optional[LedBrightness]: """Brightness of the LED.""" if self.data["led_b"] is not None: return LedBrightness(self.data["led_b"]) + + # This is the property name of the Air Purifier Pro + if self.data["bright"] is not None: + return LedBrightness(self.data["bright"]) + return None @property @@ -102,11 +112,6 @@ def child_lock(self) -> bool: """Return True if child lock is on.""" return self.data["child_lock"] == "on" - @property - def brightness(self) -> int: - """Return brightness.""" - return self.data["bright"] - @property def favorite_level(self) -> int: """Return favorite level, which is used if the mode is ``favorite``.""" @@ -125,24 +130,51 @@ def filter_hours_used(self) -> int: @property def use_time(self) -> int: - """How long the device has been active FIXME""" + """How long the device has been active in seconds.""" return self.data["use_time"] + @property + def purify_volume(self) -> int: + """The volume of purified air in cubic meter.""" + return self.data["purify_volume"] + @property def motor_speed(self) -> int: """Speed of the motor.""" return self.data["motor1_speed"] def __str__(self) -> str: - s = "" % \ - (self.power, self.aqi, self.temperature, self.humidity, self.mode, - self.led, self.led_brightness, self.buzzer, self.child_lock, - self.brightness, self.favorite_level, self.filter_life_remaining, - self.filter_hours_used, self.use_time, + s = "" % \ + (self.power, + self.aqi, + self.average_aqi, + self.temperature, + self.humidity, + self.mode, + self.led, + self.led_brightness, + self.buzzer, + self.child_lock, + self.favorite_level, + self.filter_life_remaining, + self.filter_hours_used, + self.use_time, + self.purify_volume, self.motor_speed) return s @@ -153,16 +185,22 @@ class AirPurifier(Device): def status(self) -> AirPurifierStatus: """Retrieve properties.""" - properties = ['power', 'aqi', 'humidity', 'temp_dec', - 'mode', 'led', 'led_b', 'buzzer', 'child_lock', - 'bright', 'favorite_level', 'filter1_life', - 'f1_hour_used', 'use_time', 'motor1_speed'] + properties = ['power', 'aqi', 'averageaqi', 'humidity', 'temp_dec', + 'mode', 'favorite_level', 'filter1_life', 'f1_hour_used', + 'use_time', 'motor1_speed', 'purify_volume', 'f1_hour', + # Second request + 'led', 'led_b', 'bright', 'buzzer', 'child_lock', ] values = self.send( "get_prop", - properties + properties[0:13] ) + values.extend(self.send( + "get_prop", + properties[14:] + )) + properties_count = len(properties) values_count = len(values) if properties_count != values_count: diff --git a/miio/ceil.py b/miio/ceil.py index 58fd882cf..6e783ef24 100644 --- a/miio/ceil.py +++ b/miio/ceil.py @@ -38,12 +38,12 @@ def brightness(self) -> int: @property def scene(self) -> int: - """Current scene. FIXME what is this?""" + """Current fixed scene (brightness & colortemp).""" return self.data["snm"] @property def delay_off_countdown(self) -> int: - """Countdown until turning off.""" + """Countdown until turning off in seconds.""" return self.data["dv"] @property @@ -118,7 +118,7 @@ def set_color_temperature(self, level: int): return self.send("set_cct", [level]) def delay_off(self, seconds: int): - """Set delay off seconds.""" + """Turn off delay in seconds.""" if seconds < 1: raise CeilException( @@ -127,7 +127,7 @@ def delay_off(self, seconds: int): return self.send("delay_off", [seconds]) def set_scene(self, number: int): - """Set scene number.""" + """Set a fixed scene. 4 fixed scenes are available (1-4)""" if number < 1 or number > 4: raise CeilException("Invalid fixed scene number: %s" % number) diff --git a/miio/fan.py b/miio/fan.py index f278017bf..61e074687 100644 --- a/miio/fan.py +++ b/miio/fan.py @@ -75,12 +75,12 @@ def child_lock(self) -> bool: @property def natural_level(self) -> int: - """Natural level. FIXME what is this?""" + """Fan speed in natural mode.""" return self.data["natural_level"] @property def speed_level(self) -> int: - """Speed level. FIXME how does this compare to speed?""" + """Fan speed in direct mode.""" return self.data["speed_level"] @property @@ -105,7 +105,8 @@ def poweroff_time(self) -> int: @property def speed(self) -> int: - """Current speed. FIXME how does this compare to speed_level?""" + """FIXME What is the meaning of this value? + (cp. speed_level vs. natural_level)""" return self.data["speed"] @property diff --git a/miio/philips_eyecare.py b/miio/philips_eyecare.py index 5a73455c2..9b3f1398b 100644 --- a/miio/philips_eyecare.py +++ b/miio/philips_eyecare.py @@ -36,37 +36,37 @@ def brightness(self) -> int: @property def reminder(self) -> bool: - """True if reminder is on. FIXME be more descriptive""" + """Indicates the eye fatigue notification is enabled or not.""" return self.data["notifystatus"] == "on" @property def ambient(self) -> bool: - """True if ambient is on. FIXME be more descriptive.""" + """True if the ambient light (second light source) is on.""" return self.data["ambstatus"] == "on" @property def ambient_brightness(self) -> int: - """Ambient brightness level.""" + """Brightness of the ambient light.""" return self.data["ambvalue"] @property def eyecare(self) -> bool: - """True if eyecare is on.""" + """True if the eyecare light (first light source) is on.""" return self.data["eyecare"] == "on" @property def scene(self) -> int: - """Current scene.""" + """Current fixed scene.""" return self.data["scene_num"] @property def smart_night_light(self) -> bool: - """True if smart night light is on.""" + """True if the smart night light mode is on.""" return self.data["bls"] == "on" @property def delay_off_countdown(self) -> int: - """Current delay off counter.""" + """Countdown until turning off in minutes.""" return self.data["dvalue"] def __str__(self) -> str: @@ -113,11 +113,11 @@ def off(self): return self.send("set_power", ["off"]) def eyecare_on(self): - """Eyecare on.""" + """Turn the eyecare light on.""" return self.send("set_eyecare", ["on"]) def eyecare_off(self): - """Eyecare off.""" + """Turn the eyecare light off.""" return self.send("set_eyecare", ["off"]) def set_brightness(self, level: int): @@ -128,7 +128,7 @@ def set_brightness(self, level: int): return self.send("set_bright", [level]) def set_scene(self, number: int): - """Set eyecare user scene.""" + """Set one of the fixed eyecare user scenes.""" if number < 1 or number > 4: raise PhilipsEyecareException("Invalid fixed scene number: %s" % number) @@ -144,31 +144,31 @@ def delay_off(self, minutes: int): return self.send("delay_off", [minutes]) def smart_night_light_on(self): - """Night Light On.""" + """Turn the smart night light mode on.""" return self.send("enable_bl", ["on"]) def smart_night_light_off(self): - """Night Light Off.""" + """Turn the smart night light mode off.""" return self.send("enable_bl", ["off"]) def reminder_on(self): - """Eye Fatigue Reminder On.""" + """Enable the eye fatigue reminder / notification.""" return self.send("set_notifyuser", ["on"]) def reminder_off(self): - """Eye Fatigue Reminder Off.""" + """Disable the eye fatigue reminder / notification.""" return self.send("set_notifyuser", ["off"]) def ambient_on(self): - """Amblient Light On.""" + """Turn the ambient light on.""" return self.send("enable_amb", ["on"]) def ambient_off(self): - """Ambient Light Off.""" + """Turn the ambient light off.""" return self.send("enable_amb", ["off"]) def set_ambient_brightness(self, level: int): - """Set Ambient Light brightness level.""" + """Set the brightness of the ambient light.""" if level < 1 or level > 100: raise PhilipsEyecareException( "Invalid ambient brightness: %s" % level) diff --git a/miio/philips_eyecare_cli.py b/miio/philips_eyecare_cli.py index 3ee173842..c60239c34 100644 --- a/miio/philips_eyecare_cli.py +++ b/miio/philips_eyecare_cli.py @@ -15,7 +15,7 @@ pass_dev = click.make_pass_decorator(miio.PhilipsEyecare) -def validate_bright(ctx, param, value): +def validate_brightness(ctx, param, value): value = int(value) if value < 1 or value > 100: raise click.BadParameter('Should be a positive int between 1-100.') @@ -123,9 +123,9 @@ def off(dev: miio.PhilipsEyecare): @cli.command() -@click.argument('level', callback=validate_bright, required=True,) +@click.argument('level', callback=validate_brightness, required=True,) @pass_dev -def set_bright(dev: miio.PhilipsEyecare, level): +def set_brightness(dev: miio.PhilipsEyecare, level): """Set brightness level.""" click.echo("Brightness: %s" % dev.set_brightness(level)) @@ -189,9 +189,9 @@ def ambient_off(dev: miio.PhilipsEyecare): @cli.command() -@click.argument('level', callback=validate_bright, required=True,) +@click.argument('level', callback=validate_brightness, required=True,) @pass_dev -def set_amb_bright(dev: miio.PhilipsEyecare, level): +def set_ambient_brightness(dev: miio.PhilipsEyecare, level): """Set Ambient Light brightness level.""" click.echo("Ambient Light Brightness: %s" % dev.set_ambient_brightness(level)) From eeadba7e22803b262881344973755be3d60b93c6 Mon Sep 17 00:00:00 2001 From: Sebastian Muszynski Date: Sun, 26 Nov 2017 15:48:47 +0100 Subject: [PATCH 02/11] Unused import removed. --- miio/airpurifier.py | 1 - 1 file changed, 1 deletion(-) diff --git a/miio/airpurifier.py b/miio/airpurifier.py index 345fa0f4b..5e899dc20 100644 --- a/miio/airpurifier.py +++ b/miio/airpurifier.py @@ -1,6 +1,5 @@ import logging import enum -import math from typing import Any, Dict, Optional from collections import defaultdict from .device import Device From 223e106008fd4e12827e87bd1ccefed34a6b482f Mon Sep 17 00:00:00 2001 From: Sebastian Muszynski Date: Sun, 26 Nov 2017 17:21:06 +0100 Subject: [PATCH 03/11] Unittests for the air purifier added. Property average_aqi fixed. Missing method "set_child_lock" added. --- miio/airpurifier.py | 22 +++++++--- miio/tests/test_airpurifier.py | 78 ++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+), 7 deletions(-) create mode 100644 miio/tests/test_airpurifier.py diff --git a/miio/airpurifier.py b/miio/airpurifier.py index 5e899dc20..1feb38c03 100644 --- a/miio/airpurifier.py +++ b/miio/airpurifier.py @@ -36,11 +36,12 @@ def __init__(self, data: Dict[str, Any]) -> None: Response of a Air Purifier 2: - ['power': 'on', 'aqi': 22, 'humidity': 61, 'temp_dec': 197, - 'mode': 'auto', 'led': 2, 'led_b': 'off', 'buzzer': 'off', - 'child_lock': 'off', 'bright': None, 'favorite_level': 10, - 'filter1_life': 80, 'f1_hour_used': 678, 'use_time': 2440800, - 'motor1_speed': 351, 'purify_volume': 25127] + {'power': 'on, 'aqi': 10, 'average_aqi': 8, 'humidity': 62, + 'temp_dec': 186, 'mode': 'auto', 'favorite_level': 10, + 'filter1_life': 80, 'f1_hour_used': 682, 'use_time': 2457000, + 'motor1_speed': 354, 'purify_volume': 25262, 'f1_hour': 3500, + 'led': 'off', 'led_b': 2, 'bright': None, 'buzzer': 'off', + 'child_lock': 'off'} A request is limitted to 16 properties. """ @@ -65,7 +66,7 @@ def aqi(self) -> int: @property def average_aqi(self) -> int: """Average of the air quality index.""" - return self.data["averageaqi"] + return self.data["average_aqi"] @property def humidity(self) -> int: @@ -184,7 +185,7 @@ class AirPurifier(Device): def status(self) -> AirPurifierStatus: """Retrieve properties.""" - properties = ['power', 'aqi', 'averageaqi', 'humidity', 'temp_dec', + properties = ['power', 'aqi', 'average_aqi', 'humidity', 'temp_dec', 'mode', 'favorite_level', 'filter1_life', 'f1_hour_used', 'use_time', 'motor1_speed', 'purify_volume', 'f1_hour', # Second request @@ -247,3 +248,10 @@ def set_buzzer(self, buzzer: bool): return self.send("set_buzzer", ["on"]) else: return self.send("set_buzzer", ["off"]) + + def set_child_lock(self, lock: bool): + """Set child lock on/off.""" + if lock: + return self.send("set_child_lock", ["on"]) + else: + return self.send("set_child_lock", ["off"]) diff --git a/miio/tests/test_airpurifier.py b/miio/tests/test_airpurifier.py new file mode 100644 index 000000000..078bfedbf --- /dev/null +++ b/miio/tests/test_airpurifier.py @@ -0,0 +1,78 @@ +from unittest import TestCase +from miio import Plug +from .dummies import DummyDevice +import pytest + + +class DummyAirPurifier(DummyDevice, Plug): + def __init__(self, *args, **kwargs): + self.state = { + 'power': 'on', + 'aqi': 10, + 'average_aqi': 8, + 'humidity': 62, + 'temp_dec': 186, + 'mode': 'auto', + 'favorite_level': 10, + 'filter1_life': 80, + 'f1_hour_used': 682, + 'use_time': 2457000, + 'motor1_speed': 354, + 'purify_volume': 25262, + 'f1_hour': 3500, + 'led': 'off', + 'led_b': 2, + 'bright': None, + 'buzzer': 'off', + 'child_lock': 'off' + } + self.return_values = { + 'get_prop': self._get_state, + 'set_power': lambda x: self._set_state("power", x), + 'set_mode': lambda x: self._set_state("mode", x), + 'set_led': lambda x: self._set_state("led", x), + 'set_buzzer': lambda x: self._set_state("buzzer", x), + 'set_child_lock': lambda x: self._set_state("child_lock", x), + 'set_level_favorite': + lambda x: self._set_state("favorite_level", x), + 'set_led_b': lambda x: self._set_state("led_b", x), + } + super().__init__(args, kwargs) + + +@pytest.fixture(scope="class") +def airpurifier(request): + request.cls.device = DummyAirPurifier() + # TODO add ability to test on a real device + + +@pytest.mark.usefixtures("airpurifier") +class TestAirPurifier(TestCase): + def is_on(self): + return self.device.status().is_on + + def state(self): + return self.device.status() + + def test_on(self): + self.device.off() # ensure off + + start_state = self.is_on() + assert start_state is False + + self.device.on() + assert self.is_on() is True + + def test_off(self): + self.device.on() # ensure on + + assert self.is_on() is True + self.device.off() + assert self.is_on() is False + + def test_status(self): + self.device._reset_state() + + assert self.is_on() is True + assert self.state().temperature == \ + self.device.start_state["temp_dec"] / 10.0 From 68240407fa9484d222fce854ffb5aea6aba5ca36 Mon Sep 17 00:00:00 2001 From: Sebastian Muszynski Date: Sun, 26 Nov 2017 18:05:34 +0100 Subject: [PATCH 04/11] Unit tests for the xiaomi air purifier 2 introduced. A few found bugs fixed. --- miio/airpurifier.py | 2 +- miio/tests/test_airpurifier.py | 89 ++++++++++++++++++++++++++++++++-- 2 files changed, 86 insertions(+), 5 deletions(-) diff --git a/miio/airpurifier.py b/miio/airpurifier.py index 1feb38c03..f1eeeb6ad 100644 --- a/miio/airpurifier.py +++ b/miio/airpurifier.py @@ -198,7 +198,7 @@ def status(self) -> AirPurifierStatus: values.extend(self.send( "get_prop", - properties[14:] + properties[13:] )) properties_count = len(properties) diff --git a/miio/tests/test_airpurifier.py b/miio/tests/test_airpurifier.py index 078bfedbf..d6f4d9624 100644 --- a/miio/tests/test_airpurifier.py +++ b/miio/tests/test_airpurifier.py @@ -1,10 +1,11 @@ from unittest import TestCase -from miio import Plug +from miio import AirPurifier +from miio.airpurifier import OperationMode, LedBrightness from .dummies import DummyDevice import pytest -class DummyAirPurifier(DummyDevice, Plug): +class DummyAirPurifier(DummyDevice, AirPurifier): def __init__(self, *args, **kwargs): self.state = { 'power': 'on', @@ -74,5 +75,85 @@ def test_status(self): self.device._reset_state() assert self.is_on() is True - assert self.state().temperature == \ - self.device.start_state["temp_dec"] / 10.0 + assert self.state().aqi == self.device.start_state["aqi"] + assert self.state().average_aqi == self.device.start_state["average_aqi"] + assert self.state().temperature == self.device.start_state["temp_dec"] / 10.0 + assert self.state().humidity == self.device.start_state["humidity"] + assert self.state().mode == OperationMode(self.device.start_state["mode"]) + assert self.state().favorite_level == self.device.start_state["favorite_level"] + assert self.state().filter_life_remaining == self.device.start_state["filter1_life"] + assert self.state().filter_hours_used == self.device.start_state["f1_hour_used"] + assert self.state().use_time == self.device.start_state["use_time"] + assert self.state().motor_speed == self.device.start_state["motor1_speed"] + assert self.state().purify_volume == self.device.start_state["purify_volume"] + + assert self.state().led == (self.device.start_state["led"] == 'on') + assert self.state().led_brightness == LedBrightness(self.device.start_state["led_b"]) + assert self.state().buzzer == (self.device.start_state["buzzer"] == 'on') + assert self.state().child_lock == (self.device.start_state["child_lock"] == 'on') + + def test_set_mode(self): + def mode(): + return self.device.status().mode + + self.device.set_mode(OperationMode.Silent) + assert mode() == OperationMode.Silent + + self.device.set_mode(OperationMode.Auto) + assert mode() == OperationMode.Auto + + self.device.set_mode(OperationMode.Favorite) + assert mode() == OperationMode.Favorite + + self.device.set_mode(OperationMode.Idle) + assert mode() == OperationMode.Idle + + def test_set_favorite_level(self): + def favorite_level(): + return self.device.status().favorite_level + + self.device.set_favorite_level(1) + assert favorite_level() == 1 + + def test_set_led_brightness(self): + def led_brightness(): + return self.device.status().led_brightness + + self.device.set_led_brightness(LedBrightness.Bright) + assert led_brightness() == LedBrightness.Bright + + self.device.set_led_brightness(LedBrightness.Dim) + assert led_brightness() == LedBrightness.Dim + + self.device.set_led_brightness(LedBrightness.Off) + assert led_brightness() == LedBrightness.Off + + def test_set_led(self): + def led(): + return self.device.status().led + + self.device.set_led(True) + assert led() == True + + self.device.set_led(False) + assert led() == False + + def test_set_buzzer(self): + def buzzer(): + return self.device.status().buzzer + + self.device.set_buzzer(True) + assert buzzer() == True + + self.device.set_buzzer(False) + assert buzzer() == False + + def test_set_child_lock(self): + def child_lock(): + return self.device.status().child_lock + + self.device.set_child_lock(True) + assert child_lock() == True + + self.device.set_child_lock(False) + assert child_lock() == False From e193fbee695a2fb1b172e15888f46e3fc77140de Mon Sep 17 00:00:00 2001 From: Sebastian Muszynski Date: Sun, 26 Nov 2017 18:39:05 +0100 Subject: [PATCH 05/11] Some unit tests for edge cases added. --- miio/tests/test_airpurifier.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/miio/tests/test_airpurifier.py b/miio/tests/test_airpurifier.py index d6f4d9624..18cb0af57 100644 --- a/miio/tests/test_airpurifier.py +++ b/miio/tests/test_airpurifier.py @@ -132,6 +132,7 @@ def test_set_led(self): def led(): return self.device.status().led + # The LED brightness of a Air Purifier Pro cannot be set so far. self.device.set_led(True) assert led() == True @@ -157,3 +158,29 @@ def child_lock(): self.device.set_child_lock(False) assert child_lock() == False + + def test_status_without_led_b_and_with_bright(self): + self.device._reset_state() + + self.device.state["bright"] = self.device.state["led_b"] + del self.device.state["led_b"] + + assert self.state().led_brightness == LedBrightness(self.device.start_state["led_b"]) + + def test_status_without_led_brightness_at_all(self): + self.device._reset_state() + + self.device.state["led_b"] = None + self.device.state["bright"] = None + assert self.state().led_brightness is None + + def test_status_without_temperature(self): + self.device._reset_state() + self.device.state["temp_dec"] = None + + assert self.state().temperature is None + + def test_status_string(self): + self.device._reset_state() + + assert self.state().__str__ From 12569592e8c55a9aa572cb8018c5832034ea91fb Mon Sep 17 00:00:00 2001 From: Sebastian Muszynski Date: Sun, 26 Nov 2017 21:10:15 +0100 Subject: [PATCH 06/11] Testing of the __str__ method doesn't work. --- miio/tests/test_airpurifier.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/miio/tests/test_airpurifier.py b/miio/tests/test_airpurifier.py index 18cb0af57..de59e7624 100644 --- a/miio/tests/test_airpurifier.py +++ b/miio/tests/test_airpurifier.py @@ -179,8 +179,3 @@ def test_status_without_temperature(self): self.device.state["temp_dec"] = None assert self.state().temperature is None - - def test_status_string(self): - self.device._reset_state() - - assert self.state().__str__ From 68921dbd9c68f279484a0fae4f83cd4b90a83d22 Mon Sep 17 00:00:00 2001 From: Sebastian Muszynski Date: Sun, 26 Nov 2017 21:24:42 +0100 Subject: [PATCH 07/11] Testing of limits introduced. Hound errors fixed. --- miio/airpurifier.py | 9 +++++++++ miio/tests/test_airpurifier.py | 30 ++++++++++++++++++++---------- 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/miio/airpurifier.py b/miio/airpurifier.py index f1eeeb6ad..1876d4407 100644 --- a/miio/airpurifier.py +++ b/miio/airpurifier.py @@ -7,6 +7,10 @@ _LOGGER = logging.getLogger(__name__) +class AirPurifierException(Exception): + pass + + class OperationMode(enum.Enum): Auto = 'auto' Silent = 'silent' @@ -191,6 +195,9 @@ def status(self) -> AirPurifierStatus: # Second request 'led', 'led_b', 'bright', 'buzzer', 'child_lock', ] + # A single request is limited to 16 properties. Therefore the + # properties are divided in two groups here. The second group contains + # some infrequent and independent updated properties. values = self.send( "get_prop", properties[0:13] @@ -226,6 +233,8 @@ def set_mode(self, mode: OperationMode): def set_favorite_level(self, level: int): """Set favorite level.""" + if level < 0 or level > 16: + raise AirPurifierException("Invalid favorite level: %s" % level) # Set the favorite level used when the mode is `favorite`, # should be between 0 and 16. diff --git a/miio/tests/test_airpurifier.py b/miio/tests/test_airpurifier.py index de59e7624..853061cbb 100644 --- a/miio/tests/test_airpurifier.py +++ b/miio/tests/test_airpurifier.py @@ -1,6 +1,6 @@ from unittest import TestCase from miio import AirPurifier -from miio.airpurifier import OperationMode, LedBrightness +from miio.airpurifier import OperationMode, LedBrightness, AirPurifierException from .dummies import DummyDevice import pytest @@ -112,8 +112,17 @@ def test_set_favorite_level(self): def favorite_level(): return self.device.status().favorite_level - self.device.set_favorite_level(1) - assert favorite_level() == 1 + self.device.set_favorite_level(0) + assert favorite_level() == 0 + self.device.set_favorite_level(6) + assert favorite_level() == 6 + self.device.set_favorite_level(10) + + with pytest.raises(AirPurifierException): + self.device.set_favorite_level(-1) + + with pytest.raises(AirPurifierException): + self.device.set_favorite_level(17) def test_set_led_brightness(self): def led_brightness(): @@ -134,30 +143,30 @@ def led(): # The LED brightness of a Air Purifier Pro cannot be set so far. self.device.set_led(True) - assert led() == True + assert led() is True self.device.set_led(False) - assert led() == False + assert led() is False def test_set_buzzer(self): def buzzer(): return self.device.status().buzzer self.device.set_buzzer(True) - assert buzzer() == True + assert buzzer() is True self.device.set_buzzer(False) - assert buzzer() == False + assert buzzer() is False def test_set_child_lock(self): def child_lock(): return self.device.status().child_lock self.device.set_child_lock(True) - assert child_lock() == True + assert child_lock() is True self.device.set_child_lock(False) - assert child_lock() == False + assert child_lock() is False def test_status_without_led_b_and_with_bright(self): self.device._reset_state() @@ -165,7 +174,8 @@ def test_status_without_led_b_and_with_bright(self): self.device.state["bright"] = self.device.state["led_b"] del self.device.state["led_b"] - assert self.state().led_brightness == LedBrightness(self.device.start_state["led_b"]) + assert self.state().led_brightness == \ + LedBrightness(self.device.start_state["led_b"]) def test_status_without_led_brightness_at_all(self): self.device._reset_state() From cf5a54429eda0c58e9558aec61ffde5686a439ad Mon Sep 17 00:00:00 2001 From: Sebastian Muszynski Date: Sun, 26 Nov 2017 21:26:36 +0100 Subject: [PATCH 08/11] Hound error fixed. --- miio/tests/test_airpurifier.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/miio/tests/test_airpurifier.py b/miio/tests/test_airpurifier.py index 853061cbb..48069b110 100644 --- a/miio/tests/test_airpurifier.py +++ b/miio/tests/test_airpurifier.py @@ -174,8 +174,8 @@ def test_status_without_led_b_and_with_bright(self): self.device.state["bright"] = self.device.state["led_b"] del self.device.state["led_b"] - assert self.state().led_brightness == \ - LedBrightness(self.device.start_state["led_b"]) + assert self.state().led_brightness == LedBrightness( + self.device.start_state["led_b"]) def test_status_without_led_brightness_at_all(self): self.device._reset_state() From 9f1e4c125685ccfbf9fbc80206a3bdf5a796e7f0 Mon Sep 17 00:00:00 2001 From: Sebastian Muszynski Date: Sun, 26 Nov 2017 21:52:34 +0100 Subject: [PATCH 09/11] Moved from __str__ to __repr__. --- miio/airpurifier.py | 4 ++-- miio/ceil.py | 2 +- miio/philips_bulb.py | 2 +- miio/philips_eyecare.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/miio/airpurifier.py b/miio/airpurifier.py index 1876d4407..507ae86dd 100644 --- a/miio/airpurifier.py +++ b/miio/airpurifier.py @@ -47,7 +47,7 @@ def __init__(self, data: Dict[str, Any]) -> None: 'led': 'off', 'led_b': 2, 'bright': None, 'buzzer': 'off', 'child_lock': 'off'} - A request is limitted to 16 properties. + A request is limited to 16 properties. """ self.data = data @@ -147,7 +147,7 @@ def motor_speed(self) -> int: """Speed of the motor.""" return self.data["motor1_speed"] - def __str__(self) -> str: + def __repr__(self) -> str: s = " bool: """Automatic color temperature state.""" return self.data["ac"] == 1 - def __str__(self) -> str: + def __repr__(self) -> str: s = "" % \ diff --git a/miio/philips_bulb.py b/miio/philips_bulb.py index e1e3c05a5..cd36cd918 100644 --- a/miio/philips_bulb.py +++ b/miio/philips_bulb.py @@ -41,7 +41,7 @@ def scene(self) -> int: def delay_off_countdown(self) -> int: return self.data["dv"] - def __str__(self) -> str: + def __repr__(self) -> str: s = "" % \ (self.power, self.brightness, diff --git a/miio/philips_eyecare.py b/miio/philips_eyecare.py index 9b3f1398b..dd8468d31 100644 --- a/miio/philips_eyecare.py +++ b/miio/philips_eyecare.py @@ -69,7 +69,7 @@ def delay_off_countdown(self) -> int: """Countdown until turning off in minutes.""" return self.data["dvalue"] - def __str__(self) -> str: + def __repr__(self) -> str: s = " Date: Sun, 26 Nov 2017 22:04:11 +0100 Subject: [PATCH 10/11] Use a specific code path. --- miio/tests/test_airpurifier.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/miio/tests/test_airpurifier.py b/miio/tests/test_airpurifier.py index 48069b110..235471578 100644 --- a/miio/tests/test_airpurifier.py +++ b/miio/tests/test_airpurifier.py @@ -171,11 +171,10 @@ def child_lock(): def test_status_without_led_b_and_with_bright(self): self.device._reset_state() - self.device.state["bright"] = self.device.state["led_b"] del self.device.state["led_b"] + self.device.state["bright"] = 1 - assert self.state().led_brightness == LedBrightness( - self.device.start_state["led_b"]) + assert self.state().led_brightness == LedBrightness(1) def test_status_without_led_brightness_at_all(self): self.device._reset_state() From c22086a88dde892c457e83419ec663f9d99360f5 Mon Sep 17 00:00:00 2001 From: Sebastian Muszynski Date: Sun, 26 Nov 2017 22:42:10 +0100 Subject: [PATCH 11/11] Revert "Use a specific code path." This reverts commit 779bb83b8423fb8f58d8f11612d1d3d37f209c13. --- miio/tests/test_airpurifier.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/miio/tests/test_airpurifier.py b/miio/tests/test_airpurifier.py index 235471578..48069b110 100644 --- a/miio/tests/test_airpurifier.py +++ b/miio/tests/test_airpurifier.py @@ -171,10 +171,11 @@ def child_lock(): def test_status_without_led_b_and_with_bright(self): self.device._reset_state() + self.device.state["bright"] = self.device.state["led_b"] del self.device.state["led_b"] - self.device.state["bright"] = 1 - assert self.state().led_brightness == LedBrightness(1) + assert self.state().led_brightness == LedBrightness( + self.device.start_state["led_b"]) def test_status_without_led_brightness_at_all(self): self.device._reset_state()