Skip to content

Commit

Permalink
Add zhimi.humidifier.cb1 support (Closes: #492) (#493)
Browse files Browse the repository at this point in the history
  • Loading branch information
antylama authored and syssi committed Apr 10, 2019
1 parent 11b88f6 commit feea7fc
Show file tree
Hide file tree
Showing 3 changed files with 235 additions and 9 deletions.
24 changes: 17 additions & 7 deletions miio/airhumidifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@

MODEL_HUMIDIFIER_V1 = 'zhimi.humidifier.v1'
MODEL_HUMIDIFIER_CA1 = 'zhimi.humidifier.ca1'
MODEL_HUMIDIFIER_CB1 = 'zhimi.humidifier.cb1'

AVAILABLE_PROPERTIES_COMMON = [
'power',
'mode',
'temp_dec',
'humidity',
'buzzer',
'led_b',
Expand All @@ -27,8 +27,9 @@
]

AVAILABLE_PROPERTIES = {
MODEL_HUMIDIFIER_V1: AVAILABLE_PROPERTIES_COMMON + ['trans_level', 'button_pressed'],
MODEL_HUMIDIFIER_CA1: AVAILABLE_PROPERTIES_COMMON + ['speed', 'depth', 'dry'],
MODEL_HUMIDIFIER_V1: AVAILABLE_PROPERTIES_COMMON + ['temp_dec', 'trans_level', 'button_pressed'],
MODEL_HUMIDIFIER_CA1: AVAILABLE_PROPERTIES_COMMON + ['temp_dec', 'speed', 'depth', 'dry'],
MODEL_HUMIDIFIER_CB1: AVAILABLE_PROPERTIES_COMMON + ['temperature', 'speed', 'depth', 'dry']
}


Expand Down Expand Up @@ -85,8 +86,10 @@ def mode(self) -> OperationMode:
@property
def temperature(self) -> Optional[float]:
"""Current temperature, if available."""
if self.data["temp_dec"] is not None:
if "temp_dec" in self.data and self.data["temp_dec"] is not None:
return self.data["temp_dec"] / 10.0
if "temperature" in self.data and self.data["temperature"] is not None:
return self.data["temperature"]
return None

@property
Expand Down Expand Up @@ -285,8 +288,8 @@ def status(self) -> AirHumidifierStatus:
# properties are divided into multiple requests
_props_per_request = 15

# The CA1 is limited to a single property per request
if self.model == MODEL_HUMIDIFIER_CA1:
# The CA1 and CB1 are limited to a single property per request
if self.model in [MODEL_HUMIDIFIER_CA1, MODEL_HUMIDIFIER_CB1]:
_props_per_request = 1

_props = properties.copy()
Expand Down Expand Up @@ -335,7 +338,7 @@ def set_mode(self, mode: OperationMode):
)
def set_led_brightness(self, brightness: LedBrightness):
"""Set led brightness."""
if self.model == MODEL_HUMIDIFIER_CA1:
if self.model in [MODEL_HUMIDIFIER_CA1, MODEL_HUMIDIFIER_CB1]:
return self.send("set_led_b", [str(brightness.value)])

return self.send("set_led_b", [brightness.value])
Expand Down Expand Up @@ -414,3 +417,10 @@ def __init__(self, ip: str = None, token: str = None, start_id: int = 0,
debug: int = 0, lazy_discover: bool = True) -> None:
super().__init__(ip, token, start_id, debug, lazy_discover,
model=MODEL_HUMIDIFIER_CA1)


class AirHumidifierCB1(AirHumidifier):
def __init__(self, ip: str = None, token: str = None, start_id: int = 0,
debug: int = 0, lazy_discover: bool = True) -> None:
super().__init__(ip, token, start_id, debug, lazy_discover,
model=MODEL_HUMIDIFIER_CB1)
3 changes: 2 additions & 1 deletion miio/discovery.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

from .airconditioningcompanion import (MODEL_ACPARTNER_V1, MODEL_ACPARTNER_V2, MODEL_ACPARTNER_V3, )
from .airqualitymonitor import (MODEL_AIRQUALITYMONITOR_V1, MODEL_AIRQUALITYMONITOR_B1, )
from .airhumidifier import (MODEL_HUMIDIFIER_CA1, MODEL_HUMIDIFIER_V1, )
from .airhumidifier import (MODEL_HUMIDIFIER_CB1, MODEL_HUMIDIFIER_CA1, MODEL_HUMIDIFIER_V1, )
from .chuangmi_plug import (MODEL_CHUANGMI_PLUG_V1, MODEL_CHUANGMI_PLUG_V2, MODEL_CHUANGMI_PLUG_V3,
MODEL_CHUANGMI_PLUG_M1, MODEL_CHUANGMI_PLUG_M3,
MODEL_CHUANGMI_PLUG_HMI205, )
Expand Down Expand Up @@ -53,6 +53,7 @@
"chuangmi-ir-v2": ChuangmiIr,
"zhimi-humidifier-v1": partial(AirHumidifier, model=MODEL_HUMIDIFIER_V1),
"zhimi-humidifier-ca1": partial(AirHumidifier, model=MODEL_HUMIDIFIER_CA1),
"zhimi-humidifier-cb1": partial(AirHumidifier, model=MODEL_HUMIDIFIER_CB1),
"yunmi-waterpuri-v2": WaterPurifier,
"philips-light-bulb": PhilipsBulb, # cannot be discovered via mdns
"philips-light-candle": PhilipsBulb, # cannot be discovered via mdns
Expand Down
217 changes: 216 additions & 1 deletion miio/tests/test_airhumidifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from miio import AirHumidifier
from miio.airhumidifier import (OperationMode, LedBrightness,
AirHumidifierStatus, AirHumidifierException,
MODEL_HUMIDIFIER_V1, MODEL_HUMIDIFIER_CA1)
MODEL_HUMIDIFIER_V1, MODEL_HUMIDIFIER_CA1, MODEL_HUMIDIFIER_CB1)
from .dummies import DummyDevice
from miio.device import DeviceInfo

Expand Down Expand Up @@ -427,3 +427,218 @@ def dry():

self.device.set_dry(False)
assert dry() is False


class DummyAirHumidifierCB1(DummyDevice, AirHumidifier):
def __init__(self, *args, **kwargs):
self.model = MODEL_HUMIDIFIER_CB1
self.dummy_device_info = {
'fw_ver': '1.2.9_5033',
'token': '68ffffffffffffffffffffffffffffff',
'otu_stat': [101, 74, 5343, 0, 5327, 407],
'mmfree': 228248,
'netif': {'gw': '192.168.0.1',
'localIp': '192.168.0.25',
'mask': '255.255.255.0'},
'ott_stat': [0, 0, 0, 0],
'model': 'zhimi.humidifier.v1',
'cfg_time': 0,
'life': 575661,
'ap': {'rssi': -35, 'ssid': 'ap', 'bssid': 'FF:FF:FF:FF:FF:FF'},
'wifi_fw_ver': 'SD878x-14.76.36.p84-702.1.0-WM',
'hw_ver': 'MW300',
'ot': 'otu',
'mac': '78:11:FF:FF:FF:FF'
}
self.device_info = None

self.state = {
'power': 'on',
'mode': 'medium',
'humidity': 33,
'buzzer': 'off',
'led_b': 2,
'child_lock': 'on',
'limit_hum': 40,
'use_time': 941100,
'hw_version': 0,
# Additional attributes of the zhimi.humidifier.cb1
'temperature': 29.4,
'speed': 100,
'depth': 1,
'dry': '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_b': lambda x: self._set_state("led_b", [int(x[0])]),
'set_buzzer': lambda x: self._set_state("buzzer", x),
'set_child_lock': lambda x: self._set_state("child_lock", x),
'set_limit_hum': lambda x: self._set_state("limit_hum", x),
'set_dry': lambda x: self._set_state("dry", x),
'miIO.info': self._get_device_info,
}
super().__init__(args, kwargs)

def _get_device_info(self, _):
"""Return dummy device info."""
return self.dummy_device_info


@pytest.fixture(scope="class")
def airhumidifiercb1(request):
request.cls.device = DummyAirHumidifierCB1()
# TODO add ability to test on a real device


@pytest.mark.usefixtures("airhumidifiercb1")
class TestAirHumidifierCB1(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
assert self.is_on() 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()

device_info = DeviceInfo(self.device.dummy_device_info)

assert repr(self.state()) == repr(AirHumidifierStatus(self.device.start_state, device_info))

assert self.is_on() is True
assert self.state().temperature == self.device.start_state["temperature"]
assert self.state().humidity == self.device.start_state["humidity"]
assert self.state().mode == OperationMode(self.device.start_state["mode"])
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().target_humidity == self.device.start_state["limit_hum"]
assert self.state().trans_level is None
assert self.state().motor_speed == self.device.start_state["speed"]
assert self.state().depth == self.device.start_state["depth"]
assert self.state().dry == (self.device.start_state["dry"] == 'on')
assert self.state().use_time == self.device.start_state["use_time"]
assert self.state().hardware_version == self.device.start_state["hw_version"]
assert self.state().button_pressed is None

assert self.state().firmware_version == device_info.firmware_version
assert self.state().firmware_version_major == device_info.firmware_version.rsplit('_', 1)[0]
assert self.state().firmware_version_minor == int(device_info.firmware_version.rsplit('_', 1)[1])
assert self.state().strong_mode_enabled is False

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.Medium)
assert mode() == OperationMode.Medium

self.device.set_mode(OperationMode.High)
assert mode() == OperationMode.High

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_brightness():
return self.device.status().led_brightness

self.device.set_led(True)
assert led_brightness() == LedBrightness.Bright

self.device.set_led(False)
assert led_brightness() == LedBrightness.Off

def test_set_buzzer(self):
def buzzer():
return self.device.status().buzzer

self.device.set_buzzer(True)
assert buzzer() is True

self.device.set_buzzer(False)
assert buzzer() is False

def test_status_without_temperature(self):
self.device._reset_state()
self.device.state["temperature"] = None

assert self.state().temperature is None

def test_status_without_led_brightness(self):
self.device._reset_state()
self.device.state["led_b"] = None

assert self.state().led_brightness is None

def test_set_target_humidity(self):
def target_humidity():
return self.device.status().target_humidity

self.device.set_target_humidity(30)
assert target_humidity() == 30
self.device.set_target_humidity(60)
assert target_humidity() == 60
self.device.set_target_humidity(80)
assert target_humidity() == 80

with pytest.raises(AirHumidifierException):
self.device.set_target_humidity(-1)

with pytest.raises(AirHumidifierException):
self.device.set_target_humidity(20)

with pytest.raises(AirHumidifierException):
self.device.set_target_humidity(90)

with pytest.raises(AirHumidifierException):
self.device.set_target_humidity(110)

def test_set_child_lock(self):
def child_lock():
return self.device.status().child_lock

self.device.set_child_lock(True)
assert child_lock() is True

self.device.set_child_lock(False)
assert child_lock() is False

def test_set_dry(self):
def dry():
return self.device.status().dry

self.device.set_dry(True)
assert dry() is True

self.device.set_dry(False)
assert dry() is False

0 comments on commit feea7fc

Please sign in to comment.