Skip to content

Commit

Permalink
Merge branch 'master' into feature/improve-test-coverage
Browse files Browse the repository at this point in the history
  • Loading branch information
syssi authored Mar 26, 2018
2 parents e1ef42f + 42e6035 commit 3eae367
Show file tree
Hide file tree
Showing 15 changed files with 659 additions and 626 deletions.
4 changes: 1 addition & 3 deletions miio/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@
from miio.vacuumcontainers import (VacuumStatus, ConsumableStatus, DNDStatus,
CleaningDetails, CleaningSummary, Timer)
from miio.vacuum import Vacuum, VacuumException
from miio.plug import Plug
from miio.plug_v1 import PlugV1
from miio.plug_v3 import PlugV3
from miio.chuangmi_plug import (Plug, PlugV1, PlugV3, ChuangmiPlug)
from miio.airpurifier import AirPurifier
from miio.airhumidifier import AirHumidifier
from miio.waterpurifier import WaterPurifier
Expand Down
22 changes: 13 additions & 9 deletions miio/airconditioningcompanion.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,30 +98,34 @@ def air_condition_model(self) -> str:
@property
def power(self) -> str:
"""Current power state."""
return 'on' if (self.data[1][2:3] == '1') else 'off'
return 'on' if (int(self.data[1][2:3]) == Power.On.value) else 'off'

@property
def led(self) -> str:
"""Current LED state."""
return 'on' if (self.data[1][8:9] == '1') else 'off'
return 'on' if (int(self.data[1][8:9]) == Led.On.value) else 'off'

@property
def is_on(self) -> bool:
"""True if the device is turned on."""
return self.power == 'on'

@property
def temperature(self) -> Optional[int]:
"""Current temperature."""
def target_temperature(self) -> Optional[int]:
"""Target temperature."""
try:
return int(self.data[1][6:8], 16)
except TypeError:
return None

@property
def swing_mode(self) -> bool:
"""True if swing mode is enabled."""
return self.data[1][5:6] == '0'
def swing_mode(self) -> Optional[SwingMode]:
"""Current swing mode."""
try:
mode = int(self.data[1][5:6])
return SwingMode(mode)
except TypeError:
return None

@property
def fan_speed(self) -> Optional[FanSpeed]:
Expand All @@ -147,15 +151,15 @@ def __repr__(self) -> str:
"load_power=%s, " \
"air_condition_model=%s, " \
"led=%s, " \
"temperature=%s, " \
"target_temperature=%s, " \
"swing_mode=%s, " \
"fan_speed=%s, " \
"mode=%s>" % \
(self.power,
self.load_power,
self.air_condition_model,
self.led,
self.temperature,
self.target_temperature,
self.swing_mode,
self.fan_speed,
self.mode)
Expand Down
180 changes: 180 additions & 0 deletions miio/chuangmi_plug.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
import logging
from typing import Dict, Any, Optional
from collections import defaultdict
from .device import Device
from .utils import deprecated

_LOGGER = logging.getLogger(__name__)

MODEL_CHUANGMI_PLUG_V3 = 'chuangmi.plug.v3'
MODEL_CHUANGMI_PLUG_V1 = 'chuangmi.plug.v1'
MODEL_CHUANGMI_PLUG_M1 = 'chuangmi.plug.m1'
MODEL_CHUANGMI_PLUG_V2 = 'chuangmi.plug.v2'

AVAILABLE_PROPERTIES = {
MODEL_CHUANGMI_PLUG_V1: ['on', 'usb_on', 'temperature'],
MODEL_CHUANGMI_PLUG_V3: ['on', 'usb_on', 'temperature', 'wifi_led'],
MODEL_CHUANGMI_PLUG_M1: ['power', 'temperature'],
MODEL_CHUANGMI_PLUG_V2: ['power', 'temperature'],
}


class ChuangmiPlugStatus:
"""Container for status reports from the plug."""

def __init__(self, data: Dict[str, Any]) -> None:
"""
Response of a Chuangmi Plug V1 (chuangmi.plug.v1)
{ 'power': True, 'usb_on': True, 'temperature': 32 }
Response of a Chuangmi Plug V3 (chuangmi.plug.v3):
{ 'on': True, 'usb_on': True, 'temperature': 32, 'wifi_led': True }
"""
self.data = data

@property
def power(self) -> bool:
"""Current power state."""
if "on" in self.data:
return self.data["on"]
if "power" in self.data:
return self.data["power"] == 'on'

@property
def is_on(self) -> bool:
"""True if device is on."""
return self.power

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

@property
def usb_power(self) -> Optional[bool]:
"""True if USB is on."""
if "usb_on" in self.data and self.data["usb_on"] is not None:
return self.data["usb_on"]
return None

@property
def load_power(self) -> Optional[int]:
"""Current power load, if available."""
if "load_power" in self.data and self.data["load_power"] is not None:
return self.data["load_power"]
return None

@property
def wifi_led(self) -> Optional[bool]:
"""True if the wifi led is turned on."""
if "wifi_led" in self.data and self.data["wifi_led"] is not None:
return self.data["wifi_led"] == "on"
return None

def __repr__(self) -> str:
s = "<ChuangmiPlugStatus " \
"power=%s, " \
"usb_power=%s, " \
"temperature=%s" \
"load_power=%s, " \
"wifi_led=%s>" % \
(self.power,
self.usb_power,
self.temperature,
self.load_power,
self.wifi_led)
return s


class ChuangmiPlug(Device):
"""Main class representing the Chuangmi Plug V1 and V3."""

def __init__(self, ip: str = None, token: str = None, start_id: int = 0,
debug: int = 0, lazy_discover: bool = True,
model: str = MODEL_CHUANGMI_PLUG_M1) -> None:
super().__init__(ip, token, start_id, debug, lazy_discover)

if model in AVAILABLE_PROPERTIES:
self.model = model
else:
self.model = MODEL_CHUANGMI_PLUG_M1

def status(self) -> ChuangmiPlugStatus:
"""Retrieve properties."""
properties = AVAILABLE_PROPERTIES[self.model]
values = self.send(
"get_prop",
properties
)

properties_count = len(properties)
values_count = len(values)
if properties_count != values_count:
_LOGGER.debug(
"Count (%s) of requested properties does not match the "
"count (%s) of received values.",
properties_count, values_count)

if self.model == MODEL_CHUANGMI_PLUG_V3:
load_power = self.send("get_power", []) # Response: [300]
if len(load_power) == 1:
properties.append('load_power')
values.append(load_power[0])

return ChuangmiPlugStatus(
defaultdict(lambda: None, zip(properties, values)))

def on(self):
"""Power on."""
if self.model == MODEL_CHUANGMI_PLUG_V1:
return self.send("set_on", [])

return self.send("set_power", ["on"])

def off(self):
"""Power off."""
if self.model == MODEL_CHUANGMI_PLUG_V1:
return self.send("set_off", [])

return self.send("set_power", ["off"])

def usb_on(self):
"""Power on."""
return self.send("set_usb_on", [])

def usb_off(self):
"""Power off."""
return self.send("set_usb_off", [])

def set_wifi_led(self, led: bool):
"""Set the wifi led on/off."""
if led:
return self.send("set_wifi_led", ["on"])
else:
return self.send("set_wifi_led", ["off"])


@deprecated("This device class is deprecated. Please use the ChuangmiPlug "
"class in future and select a model by parameter 'model'.")
class Plug(ChuangmiPlug):
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_CHUANGMI_PLUG_M1)


@deprecated("This device class is deprecated. Please use the ChuangmiPlug "
"class in future and select a model by parameter 'model'.")
class PlugV1(ChuangmiPlug):
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_CHUANGMI_PLUG_V1)


@deprecated("This device class is deprecated. Please use the ChuangmiPlug "
"class in future and select a model by parameter 'model'.")
class PlugV3(ChuangmiPlug):
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_CHUANGMI_PLUG_V3)
29 changes: 18 additions & 11 deletions miio/discovery.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,13 @@
import ipaddress
import inspect
import codecs
from . import (Device, Vacuum, Plug, PlugV1, PlugV3, PowerStrip, AirPurifier, Ceil,
from . import (Device, Vacuum, ChuangmiPlug, PowerStrip, AirPurifier, Ceil,
PhilipsBulb, PhilipsEyecare, ChuangmiIr, AirHumidifier,
WaterPurifier, WifiSpeaker, Yeelight)
from .chuangmi_plug import (MODEL_CHUANGMI_PLUG_V1, MODEL_CHUANGMI_PLUG_V3,
MODEL_CHUANGMI_PLUG_M1)

from functools import partial
from typing import Union, Callable, Dict, Optional # noqa: F401


Expand All @@ -15,11 +19,11 @@
DEVICE_MAP = {
"rockrobo-vacuum-v1": Vacuum,
"roborock-vacuum-s5": Vacuum,
"chuangmi-plug-m1": Plug,
"chuangmi-plug-v2": Plug,
"chuangmi-plug-v1": PlugV1,
"chuangmi-plug_": PlugV1,
"chuangmi-plug-v3": PlugV3,
"chuangmi-plug-m1": partial(ChuangmiPlug, model=MODEL_CHUANGMI_PLUG_M1),
"chuangmi-plug-v2": partial(ChuangmiPlug, model=MODEL_CHUANGMI_PLUG_M1),
"chuangmi-plug-v1": partial(ChuangmiPlug, model=MODEL_CHUANGMI_PLUG_V1),
"chuangmi-plug_": partial(ChuangmiPlug, model=MODEL_CHUANGMI_PLUG_V1),
"chuangmi-plug-v3": partial(ChuangmiPlug, model=MODEL_CHUANGMI_PLUG_V3),
"qmi-powerstrip-v1": PowerStrip,
"zimi-powerstrip-v2": PowerStrip,
"zhimi-airpurifier-m1": AirPurifier, # mini model
Expand Down Expand Up @@ -63,13 +67,16 @@ def other_package_info(info, desc):
desc)


def create_device(addr, device_cls) -> Device:
def create_device(name: str, addr: str, device_cls: partial) -> Device:
"""Return a device object for a zeroconf entry."""
_LOGGER.debug("Found a supported '%s', using '%s' class",
name, device_cls.func.__name__)

dev = device_cls(ip=addr)
m = dev.do_discover()
dev.token = m.checksum
_LOGGER.info("Found a supported '%s' at %s - token: %s",
device_cls.__name__,
device_cls.func.__name__,
addr,
pretty_token(dev.token))
return dev
Expand All @@ -87,9 +94,9 @@ def check_and_create_device(self, info, addr) -> Optional[Device]:
for identifier, v in DEVICE_MAP.items():
if name.startswith(identifier):
if inspect.isclass(v):
_LOGGER.debug("Found a supported '%s', using '%s' class",
name, v.__name__)
return create_device(addr, v)
return create_device(name, addr, partial(v))
elif type(v) is partial and inspect.isclass(v.func):
return create_device(name, addr, v)
elif callable(v):
dev = Device(ip=addr)
_LOGGER.info("%s: token: %s",
Expand Down
65 changes: 0 additions & 65 deletions miio/plug.py

This file was deleted.

Loading

0 comments on commit 3eae367

Please sign in to comment.