Skip to content

Commit

Permalink
Xiaomi Mi Smart Pedestal Fan: Add SA1 (zimi.fan.sa1) support (#354)
Browse files Browse the repository at this point in the history
  • Loading branch information
syssi authored Aug 11, 2018
1 parent e6220e9 commit 2b6b068
Show file tree
Hide file tree
Showing 5 changed files with 274 additions and 25 deletions.
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ Supported devices
- Xiaomi Philips LED Ball Lamp (:class:`miio.philips_bulb`)
- Xiaomi Philips Zhirui Smart LED Bulb E14 Candle Lamp (:class:`miio.philips_bulb`)
- Xiaomi Universal IR Remote Controller (Chuangmi IR) (:class:`miio.chuangmi_ir`)
- Xiaomi Mi Smart Pedestal Fan (:class:`miio.fan`)
- Xiaomi Mi Smart Pedestal Fan V2, V3 and SA1 (:class:`miio.fan`)
- Xiaomi Mi Air Humidifier (:class:`miio.airhumidifier`)
- Xiaomi Mi Water Purifier (Basic support: Turn on & off) (:class:`miio.waterpurifier`)
- Xiaomi PM2.5 Air Quality Monitor (:class:`miio.airqualitymonitor`)
Expand Down
2 changes: 1 addition & 1 deletion miio/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from miio.chuangmi_plug import (Plug, PlugV1, PlugV3, ChuangmiPlug)
from miio.cooker import Cooker
from miio.device import Device, DeviceException
from miio.fan import Fan
from miio.fan import (Fan, FanV2, FanSA1)
from miio.philips_bulb import PhilipsBulb
from miio.philips_eyecare import PhilipsEyecare
from miio.powerstrip import PowerStrip
Expand Down
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 .chuangmi_plug import (MODEL_CHUANGMI_PLUG_V1, MODEL_CHUANGMI_PLUG_V3,
MODEL_CHUANGMI_PLUG_M1, )
from .fan import (MODEL_FAN_V2, MODEL_FAN_V3, )
from .fan import (MODEL_FAN_V2, MODEL_FAN_V3, MODEL_FAN_SA1, )
from .powerstrip import (MODEL_POWER_STRIP_V1, MODEL_POWER_STRIP_V2, )

_LOGGER = logging.getLogger(__name__)
Expand Down Expand Up @@ -67,6 +67,7 @@
"yeelink-light-": Yeelight,
"zhimi-fan-v2": partial(Fan, model=MODEL_FAN_V2),
"zhimi-fan-v3": partial(Fan, model=MODEL_FAN_V3),
"zhimi-fan-sa1": partial(Fan, model=MODEL_FAN_SA1),
"lumi-gateway-": lambda x: other_package_info(
x, "https://github.com/Danielhiversen/PyXiaomiGateway")
} # type: Dict[str, Union[Callable, Device]]
Expand Down
83 changes: 62 additions & 21 deletions miio/fan.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

MODEL_FAN_V2 = 'zimi.fan.v2'
MODEL_FAN_V3 = 'zimi.fan.v3'
MODEL_FAN_SA1 = 'zimi.fan.sa1'

AVAILABLE_PROPERTIES_COMMON = [
'temp_dec',
Expand All @@ -35,6 +36,8 @@
AVAILABLE_PROPERTIES = {
MODEL_FAN_V2: ['led', 'bat_state'] + AVAILABLE_PROPERTIES_COMMON,
MODEL_FAN_V3: AVAILABLE_PROPERTIES_COMMON,
MODEL_FAN_SA1: ['angle', 'speed', 'poweroff_time', 'power', 'ac_power', 'angle_enable',
'speed_level', 'natural_level', 'child_lock', 'buzzer', 'led_b', 'use_time'],
}


Expand Down Expand Up @@ -66,6 +69,11 @@ def __init__(self, data: Dict[str, Any]) -> None:
'child_lock': 'off', 'buzzer': 'on', 'led_b': 1, 'led': None,
'natural_enable': None, 'use_time': 0, 'bat_charge': 'complete',
'bat_state': None, 'button_pressed':'speed'}
Response of a Fan (zhimi.fan.sa1):
{'angle': 120, 'speed': 277, 'poweroff_time': 0, 'power': 'on',
'ac_power': 'on', 'angle_enable': 'off', 'speed_level': 1, 'natural_level': 2,
'child_lock': 'off', 'buzzer': 0, 'led_b': 0, 'use_time': 2318}
"""
self.data = data

Expand All @@ -80,14 +88,18 @@ def is_on(self) -> bool:
return self.power == "on"

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

@property
def temperature(self) -> float:
def temperature(self) -> Optional[float]:
"""Current temperature, if available."""
return self.data["temp_dec"] / 10.0
if "temp_dec" in self.data and self.data["temp_dec"] is not None:
return self.data["temp_dec"] / 10.0
return None

@property
def led(self) -> Optional[bool]:
Expand All @@ -106,37 +118,40 @@ def led_brightness(self) -> Optional[LedBrightness]:
@property
def buzzer(self) -> bool:
"""True if buzzer is turned on."""
return self.data["buzzer"] == "on"
return self.data["buzzer"] in ["on", 1, 2]

@property
def child_lock(self) -> bool:
"""True if child lock is on."""
return self.data["child_lock"] == "on"

@property
def natural_speed(self) -> int:
def natural_speed(self) -> Optional[int]:
"""Speed level in natural mode."""
return self.data["natural_level"]
if "natural_level" in self.data and self.data["natural_level"] is not None:
return self.data["natural_level"]

@property
def direct_speed(self) -> int:
def direct_speed(self) -> Optional[int]:
"""Speed level in direct mode."""
return self.data["speed_level"]
if "speed_level" in self.data and self.data["speed_level"] is not None:
return self.data["speed_level"]

@property
def oscillate(self) -> bool:
"""True if oscillation is enabled."""
return self.data["angle_enable"] == "on"

@property
def battery(self) -> int:
def battery(self) -> Optional[int]:
"""Current battery level."""
return self.data["battery"]
if "battery" in self.data and self.data["battery"] is not None:
return self.data["battery"]

@property
def battery_charge(self) -> Optional[str]:
"""State of the battery charger, if available."""
if self.data["bat_charge"] is not None:
if "bat_charge" in self.data and self.data["bat_charge"] is not None:
return self.data["bat_charge"]
return None

Expand Down Expand Up @@ -249,11 +264,11 @@ def __init__(self, ip: str = None, token: str = None, start_id: int = 0,
"LED brightness: {result.led_brightness}\n"
"Buzzer: {result.buzzer}\n"
"Child lock: {result.child_lock}\n"
"Natural level: {result.natural_level}\n"
"Speed level: {result.speed_level}\n"
"Oscillate: {result.oscillate}\n"
"Power-off time: {result.poweroff_time}\n"
"Speed: {result.speed}\n"
"Natural speed: {result.natural_speed}\n"
"Direct speed: {result.direct_speed}\n"
"Oscillate: {result.oscillate}\n"
"Power-off time: {result.delay_off_countdown}\n"
"Angle: {result.angle}\n"
)
)
Expand All @@ -263,11 +278,17 @@ def status(self) -> FanStatus:

# A single request is limited to 16 properties. Therefore the
# properties are divided into multiple requests
_props_per_request = 15

# The SA1 is limited to a single property per request
if self.model == MODEL_FAN_SA1:
_props_per_request = 1

_props = properties.copy()
values = []
while _props:
values.extend(self.send("get_prop", _props[:15]))
_props[:] = _props[15:]
values.extend(self.send("get_prop", _props[:_props_per_request]))
_props[:] = _props[_props_per_request:]

properties_count = len(properties)
values_count = len(values)
Expand Down Expand Up @@ -340,8 +361,8 @@ def set_angle(self, angle: int):
@command(
click.argument("oscillate", type=bool),
default_output=format_output(
lambda lock: "Turning on oscillate"
if lock else "Turning off oscillate"
lambda oscillate: "Turning on oscillate"
if oscillate else "Turning off oscillate"
)
)
def set_oscillate(self, oscillate: bool):
Expand All @@ -368,7 +389,7 @@ def set_led_brightness(self, brightness: LedBrightness):
)
)
def set_led(self, led: bool):
"""Turn led on/off."""
"""Turn led on/off. Not supported by model SA1."""
if led:
return self.send("set_led", ['on'])
else:
Expand All @@ -383,6 +404,12 @@ def set_led(self, led: bool):
)
def set_buzzer(self, buzzer: bool):
"""Set buzzer on/off."""
if self.model == MODEL_FAN_SA1:
if buzzer:
return self.send("set_buzzer", [2])
else:
return self.send("set_buzzer", [0])

if buzzer:
return self.send("set_buzzer", ["on"])
else:
Expand Down Expand Up @@ -415,3 +442,17 @@ def delay_off(self, seconds: int):
"Invalid value for a delayed turn off: %s" % seconds)

return self.send("set_poweroff_time", [seconds])


class FanV2(Fan):
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_FAN_V2)


class FanSA1(Fan):
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_FAN_SA1)
Loading

0 comments on commit 2b6b068

Please sign in to comment.