Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add zhimi.humidifier.cb1 support #493

Merged
merged 3 commits into from
Apr 10, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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'],

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

line too long (101 > 100 characters)

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):
syssi marked this conversation as resolved.
Show resolved Hide resolved
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])

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

line too long (105 > 100 characters)

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