Skip to content

Commit

Permalink
Features of mixed air purifier models added (#188)
Browse files Browse the repository at this point in the history
  • Loading branch information
syssi authored Jan 28, 2018
1 parent 840f24b commit ae5f4ba
Show file tree
Hide file tree
Showing 3 changed files with 121 additions and 18 deletions.
83 changes: 75 additions & 8 deletions miio/airpurifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,13 +103,22 @@ def temperature(self) -> Optional[float]:
"""Current temperature, if available."""
if self.data["temp_dec"] is not None:
return self.data["temp_dec"] / 10.0

return None

@property
def mode(self) -> OperationMode:
"""Current operation mode."""
return OperationMode(self.data["mode"])

@property
def sleep_mode(self) -> Optional[OperationMode]:

This comment has been minimized.

Copy link
@yawor

yawor Jan 28, 2018

Contributor

sleep_mode doesn't use the same values as mode. For example, my APPro returns poweroff in sleep_mode today and status __repr__ raises ValueError: 'poweroff' is not a valid OperationMode.

This comment has been minimized.

Copy link
@syssi

syssi Jan 28, 2018

Author Collaborator

Damn! Thanks for your hint! I will care about.

This comment has been minimized.

Copy link
@syssi

syssi Jan 28, 2018

Author Collaborator

Do you know how to control the sleep mode? Did you understand the learn mode and count?

This comment has been minimized.

Copy link
@yawor

yawor Jan 28, 2018

Contributor

This can't be controlled manually. There are no set_sleep_* methods. It's a learning function (related to act_sleep property). I don't know if this is built into the firmware or maybe it's processed on the Xiaomi server side, but something observes trends in user's behaviour (when user changes mode to silent or turns on/off the device during the day) and it sets the sleep_* properties. When the act_sleep is set to single then the device should automatically change modes or turn itself on and off depending on the learned behaviours.
At least that's the theory. I haven't tried it as I've written my own control logic in HA + Appdaemon (I have my AP always on favorite mode and I'm controlling the favorite_level).

"""Operation mode of the sleep state. (Idle vs. Silent)"""
if self.data["sleep_mode"] is not None:
return OperationMode(self.data["sleep_mode"])

return None

@property
def led(self) -> bool:
"""Return True if LED is on."""
Expand Down Expand Up @@ -209,6 +218,33 @@ def learn_mode(self) -> bool:
"""Return True if Learn Mode is enabled."""
return self.data["act_sleep"] == "single"

@property
def sleep_time(self) -> Optional[int]:
return self.data["sleep_time"]

@property
def sleep_mode_learn_count(self) -> Optional[int]:
return self.data["sleep_data_num"]

@property
def extra_features(self) -> Optional[int]:
return self.data["app_extra"]

@property
def turbo_mode_supported(self) -> Optional[bool]:

This comment has been minimized.

Copy link
@yawor

yawor Jan 28, 2018

Contributor

I don't think this is a correct name for this property. If I understand the code correctly, it doesn't say if the turbo mode is supported but controls state of the turbo mode if it is supported by the device.

This comment has been minimized.

Copy link
@syssi

syssi Jan 28, 2018

Author Collaborator

I tried to explain it here:

    def set_extra_features(self, value: int):
        """Storage register to enable extra features at the app.

It's probably not enough. ;-)

if self.data["app_extra"] is not None:
return self.data["app_extra"] == 1

return None

@property
def auto_detect(self) -> Optional[bool]:
"""Return True if auto detect is enabled."""
if self.data["act_det"] is not None:
return self.data["act_det"] == "on"

return None

@classmethod
def _get_filter_type(cls, product_id: str) -> FilterType:
ft = cls._filter_type_cache.get(product_id, None)
Expand Down Expand Up @@ -242,7 +278,13 @@ def __repr__(self) -> str:
"filter_rfid_product_id=%s, " \
"filter_rfid_tag=%s, " \
"filter_type=%s, " \
"learn_mode=%s>" % \
"learn_mode=%s, " \
"sleep_mode=%s, " \
"sleep_time=%s, " \
"sleep_mode_learn_count=%s, " \
"extra_features=%s, " \
"turbo_mode_supported=%s, " \
"auto_detect=%s>" % \
(self.power,
self.aqi,
self.average_aqi,
Expand All @@ -265,7 +307,13 @@ def __repr__(self) -> str:
self.filter_rfid_product_id,
self.filter_rfid_tag,
self.filter_type,
self.learn_mode)
self.learn_mode,
self.sleep_mode,
self.sleep_time,
self.sleep_mode_learn_count,
self.extra_features,
self.turbo_mode_supported,
self.auto_detect)
return s


Expand All @@ -278,19 +326,19 @@ def status(self) -> AirPurifierStatus:
properties = ['power', 'aqi', 'average_aqi', 'humidity', 'temp_dec',
'mode', 'favorite_level', 'filter1_life', 'f1_hour_used',
'use_time', 'motor1_speed', 'motor2_speed',
'purify_volume',
'purify_volume', 'f1_hour', 'led',
# Second request
'f1_hour', 'led', 'led_b', 'bright', 'buzzer',
'child_lock', 'volume', 'rfid_product_id', 'rfid_tag',
'act_sleep']
'led_b', 'bright', 'buzzer', 'child_lock', 'volume',
'rfid_product_id', 'rfid_tag', 'act_sleep', 'sleep_mode',
'sleep_time', 'sleep_data_num', 'app_extra', 'act_det']

# A single request is limited to 16 properties. Therefore the
# properties are divided into multiple requests
_props = properties.copy()
values = []
while _props:
values.extend(self.send("get_prop", _props[:13]))
_props[:] = _props[13:]
values.extend(self.send("get_prop", _props[:15]))
_props[:] = _props[15:]

properties_count = len(properties)
values_count = len(values)
Expand Down Expand Up @@ -320,6 +368,8 @@ def set_favorite_level(self, level: int):
if level < 0 or level > 16:
raise AirPurifierException("Invalid favorite level: %s" % level)

# Possible alternative property: set_speed_favorite

# Set the favorite level used when the mode is `favorite`,
# should be between 0 and 16.
return self.send("set_level_favorite", [level]) # 0 ... 16
Expand Down Expand Up @@ -363,6 +413,23 @@ def set_learn_mode(self, learn_mode: bool):
else:
return self.send("set_act_sleep", ["close"])

def set_auto_detect(self, auto_detect: bool):
"""Set auto detect on/off. It's a feature of the AirPurifier V1 & V3"""
if auto_detect:
return self.send("set_act_det", ["on"])
else:
return self.send("set_act_det", ["off"])

def set_extra_features(self, value: int):
"""Storage register to enable extra features at the app.
app_extra=1 unlocks a turbo mode supported feature
"""
if value < 0:
raise AirPurifierException("Invalid app extra value: %s" % value)

return self.send("set_app_extra", [value])

def reset_filter(self):
"""Resets filter hours used and remaining life."""
return self.send('reset_filter1')
18 changes: 11 additions & 7 deletions miio/discovery.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,17 @@
"chuangmi-plug_": PlugV1,
"qmi-powerstrip-v1": PowerStrip,
"zimi-powerstrip-v2": PowerStrip,
"zhimi-airpurifier-m1": AirPurifier,
"zhimi-airpurifier-m2": AirPurifier,
"zhimi-airpurifier-ma2": AirPurifier,
"zhimi-airpurifier-v1": AirPurifier,
"zhimi-airpurifier-v2": AirPurifier,
"zhimi-airpurifier-v3": AirPurifier,
"zhimi-airpurifier-v6": AirPurifier,
"zhimi-airpurifier-m1": AirPurifier, # mini model
"zhimi-airpurifier-m2": AirPurifier, # mini model 2
"zhimi-airpurifier-ma1": AirPurifier, # ms model
"zhimi-airpurifier-ma2": AirPurifier, # ms model 2
"zhimi-airpurifier-sa1": AirPurifier, # super model
"zhimi-airpurifier-sa2": AirPurifier, # super model 2
"zhimi-airpurifier-v1": AirPurifier, # v1
"zhimi-airpurifier-v2": AirPurifier, # v2
"zhimi-airpurifier-v3": AirPurifier, # v3
"zhimi-airpurifier-v5": AirPurifier, # v5
"zhimi-airpurifier-v6": AirPurifier, # v6
"chuangmi-ir-v2": ChuangmiIr,
"zhimi-humidifier-v1": AirHumidifier,
"yunmi-waterpuri-v2": WaterPurifier,
Expand Down
38 changes: 35 additions & 3 deletions miio/tests/test_airpurifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ def __init__(self, *args, **kwargs):
'rfid_product_id': '0:0:41:30',
'rfid_tag': '10:20:30:40:50:60:7',
'act_sleep': 'close',
'sleep_mode': 'idle',
'sleep_time': 83890,
'sleep_data_num': 22,
'app_extra': 1,
'act_det': 'off'
}
self.return_values = {
'get_prop': self._get_state,
Expand All @@ -49,7 +54,9 @@ def __init__(self, *args, **kwargs):
'reset_filter1': lambda x: (
self._set_state('f1_hour_used', [0]),
self._set_state('filter1_life', [100])
)
),
'set_act_det': lambda x: self._set_state("act_det", x),
'set_app_extra': lambda x: self._set_state("app_extra", x),
}
super().__init__(args, kwargs)

Expand Down Expand Up @@ -100,15 +107,19 @@ def test_status(self):
assert self.state().motor_speed == self.device.start_state["motor1_speed"]
assert self.state().motor2_speed == self.device.start_state["motor2_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')
assert self.state().illuminance == self.device.start_state["bright"]
assert self.state().volume == self.device.start_state["volume"]
assert self.state().filter_rfid_product_id == self.device.start_state["rfid_product_id"]
assert self.state().filter_rfid_tag == self.device.start_state["rfid_tag"]
assert self.state().sleep_mode == OperationMode(self.device.start_state["sleep_mode"])
assert self.state().sleep_time == self.device.start_state["sleep_time"]
assert self.state().sleep_mode_learn_count == self.device.start_state["sleep_data_num"]
assert self.state().extra_features == self.device.start_state["app_extra"]
assert self.state().turbo_mode_supported == (self.device.start_state["app_extra"] == 1)
assert self.state().auto_detect == (self.device.start_state["act_det"] == 'on')

def test_set_mode(self):
def mode():
Expand Down Expand Up @@ -213,6 +224,27 @@ def learn_mode():
self.device.set_learn_mode(False)
assert learn_mode() is False

def test_set_auto_detect(self):
def auto_detect():
return self.device.status().auto_detect

self.device.set_auto_detect(True)
assert auto_detect() is True

self.device.set_auto_detect(False)
assert auto_detect() is False

def test_set_extra_features(self):
def extra_features():
return self.device.status().extra_features

self.device.set_extra_features(0)
assert extra_features() == 0
self.device.set_extra_features(1)
assert extra_features() == 1
self.device.set_extra_features(2)
assert extra_features() == 2

def test_reset_filter(self):
def filter_hours_used():
return self.device.status().filter_hours_used
Expand Down

0 comments on commit ae5f4ba

Please sign in to comment.