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

Xiaomi Mi Smart Pedestal Fan: Add SA1 (zimi.fan.sa1) support #354

Merged
merged 12 commits into from
Aug 11, 2018
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),

Choose a reason for hiding this comment

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

undefined name '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