Skip to content

Commit

Permalink
Additional property "average_aqi" and "purify_volume" of the Xiaomi A…
Browse files Browse the repository at this point in the history
…ir Purifier 2 introduced. (#132)

* Unit tests for the Xiaomi Air Purifier added.
* Missing method "set_child_lock" added.
* Some docstrings updated.
  • Loading branch information
syssi committed Nov 26, 2017
1 parent 3df66fd commit 5ba379e
Show file tree
Hide file tree
Showing 7 changed files with 306 additions and 60 deletions.
110 changes: 82 additions & 28 deletions miio/airpurifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
_LOGGER = logging.getLogger(__name__)


class AirPurifierException(Exception):
pass


class OperationMode(enum.Enum):
Auto = 'auto'
Silent = 'silent'
Expand Down Expand Up @@ -36,14 +40,14 @@ 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': 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'}
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 limited to 16 properties.
"""

self.data = data
Expand All @@ -63,6 +67,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["average_aqi"]

@property
def humidity(self) -> int:
"""Current humidity."""
Expand Down Expand Up @@ -90,6 +99,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
Expand All @@ -102,11 +116,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``."""
Expand All @@ -125,24 +134,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 = "<AirPurifierStatus power=%s, aqi=%s temperature=%s, " \
"humidity=%s%%, mode=%s, led=%s, led_brightness=%s, buzzer=%s, " \
"child_lock=%s, brightness=%s, favorite_level=%s, " \
"filter_life_remaining=%s, filter_hours_used=%s, " \
"use_time=%s, motor_speed=%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,
def __repr__(self) -> str:
s = "<AirPurifierStatus power=%s, " \
"aqi=%s," \
"average_aqi=%s," \
"temperature=%s, " \
"humidity=%s%%," \
"mode=%s," \
"led=%s," \
"led_brightness=%s," \
"buzzer=%s, " \
"child_lock=%s," \
"favorite_level=%s," \
"filter_life_remaining=%s, " \
"filter_hours_used=%s, " \
"use_time=%s, " \
"purify_volume=%s, " \
"motor_speed=%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

Expand All @@ -153,16 +189,25 @@ 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', 'average_aqi', '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', ]

# 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
properties[0:13]
)

values.extend(self.send(
"get_prop",
properties[13:]
))

properties_count = len(properties)
values_count = len(values)
if properties_count != values_count:
Expand All @@ -188,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.
Expand All @@ -210,3 +257,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"])
10 changes: 5 additions & 5 deletions miio/ceil.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -61,7 +61,7 @@ def automatic_color_temperature(self) -> bool:
"""Automatic color temperature state."""
return self.data["ac"] == 1

def __str__(self) -> str:
def __repr__(self) -> str:
s = "<CeilStatus power=%s, brightness=%s, " \
"color_temperature=%s, scene=%s, delay_off_countdown=%s, " \
"smart_night_light=%s, automatic_color_temperature=%s>" % \
Expand Down Expand Up @@ -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(
Expand All @@ -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)

Expand Down
7 changes: 4 additions & 3 deletions miio/fan.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion miio/philips_bulb.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = "<PhilipsBulbStatus power=%s, brightness=%s, " \
"color_temperature=%s, scene=%s, delay_off_countdown=%s>" % \
(self.power, self.brightness,
Expand Down
36 changes: 18 additions & 18 deletions miio/philips_eyecare.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,40 +36,40 @@ 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:
def __repr__(self) -> str:
s = "<PhilipsEyecareStatus power=%s, brightness=%s, " \
"notify=%s, ambient=%s, ambient_brightness=%s, " \
"eyecare=%s, scene=%s, smart_night_light=%s, " \
Expand Down Expand Up @@ -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):
Expand All @@ -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)

Expand All @@ -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)
Expand Down

0 comments on commit 5ba379e

Please sign in to comment.