Skip to content

Commit

Permalink
Add basic dmaker.fan.1c support (#1012)
Browse files Browse the repository at this point in the history
  • Loading branch information
syssi authored May 3, 2021
1 parent 9ba6741 commit 619f03a
Show file tree
Hide file tree
Showing 5 changed files with 344 additions and 24 deletions.
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ Supported devices
- Xiaomi Philips Zhirui Bedroom Smart Lamp
- Huayi Huizuo Lamps
- Xiaomi Universal IR Remote Controller (Chuangmi IR)
- Xiaomi Mi Smart Pedestal Fan V2, V3, SA1, ZA1, ZA3, ZA4, P5, P9, P10, P11
- Xiaomi Mi Smart Pedestal Fan V2, V3, SA1, ZA1, ZA3, ZA4, 1C, P5, P9, P10, P11
- Xiaomi Rosou SS4 Ventilator (leshow.fan.ss4)
- Xiaomi Mi Air Humidifier V1, CA1, CA4, CB1, MJJSQ, JSQ, JSQ1, JSQ001
- Xiaomi Mi Water Purifier (Basic support: Turn on & off)
Expand Down
2 changes: 1 addition & 1 deletion miio/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
from miio.exceptions import DeviceError, DeviceException
from miio.fan import Fan, FanP5, FanSA1, FanV2, FanZA1, FanZA4
from miio.fan_leshow import FanLeshow
from miio.fan_miot import FanMiot, FanP9, FanP10, FanP11
from miio.fan_miot import Fan1C, FanMiot, FanP9, FanP10, FanP11
from miio.gateway import Gateway
from miio.heater import Heater
from miio.heater_miot import HeaterMiot
Expand Down
3 changes: 2 additions & 1 deletion miio/discovery.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@
MODEL_FAN_ZA3,
MODEL_FAN_ZA4,
)
from .fan_miot import MODEL_FAN_P9, MODEL_FAN_P10, MODEL_FAN_P11
from .fan_miot import MODEL_FAN_1C, MODEL_FAN_P9, MODEL_FAN_P10, MODEL_FAN_P11
from .heater import MODEL_HEATER_MA1, MODEL_HEATER_ZA1
from .powerstrip import MODEL_POWER_STRIP_V1, MODEL_POWER_STRIP_V2
from .toiletlid import MODEL_TOILETLID_V1
Expand Down Expand Up @@ -180,6 +180,7 @@
"zhimi-fan-za1": partial(Fan, model=MODEL_FAN_ZA1),
"zhimi-fan-za3": partial(Fan, model=MODEL_FAN_ZA3),
"zhimi-fan-za4": partial(Fan, model=MODEL_FAN_ZA4),
"dmaker-fan-1c": partial(FanMiot, model=MODEL_FAN_1C),
"dmaker-fan-p5": partial(Fan, model=MODEL_FAN_P5),
"dmaker-fan-p9": partial(FanMiot, model=MODEL_FAN_P9),
"dmaker-fan-p10": partial(FanMiot, model=MODEL_FAN_P10),
Expand Down
207 changes: 205 additions & 2 deletions miio/fan_miot.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,20 @@
MODEL_FAN_P9 = "dmaker.fan.p9"
MODEL_FAN_P10 = "dmaker.fan.p10"
MODEL_FAN_P11 = "dmaker.fan.p11"
MODEL_FAN_1C = "dmaker.fan.1c"

MIOT_MAPPING = {
MODEL_FAN_1C: {
# https://miot-spec.org/miot-spec-v2/instance?type=urn:miot-spec-v2:device:fan:0000A005:dmaker-1c:1
"power": {"siid": 2, "piid": 1},
"fan_level": {"siid": 2, "piid": 2},
"child_lock": {"siid": 3, "piid": 1},
"swing_mode": {"siid": 2, "piid": 3},
"power_off_time": {"siid": 2, "piid": 10},
"buzzer": {"siid": 2, "piid": 11},
"light": {"siid": 2, "piid": 12},
"mode": {"siid": 2, "piid": 7},
},
MODEL_FAN_P9: {
# Source https://miot-spec.org/miot-spec-v2/instance?type=urn:miot-spec-v2:device:fan:0000A005:dmaker-p9:1
"power": {"siid": 2, "piid": 1},
Expand Down Expand Up @@ -128,7 +140,7 @@ def angle(self) -> int:

@property
def delay_off_countdown(self) -> int:
"""Countdown until turning off in seconds."""
"""Countdown until turning off in minutes."""
return self.data["power_off_time"]

@property
Expand All @@ -147,6 +159,76 @@ def child_lock(self) -> bool:
return self.data["child_lock"]


class FanStatus1C(DeviceStatus):
"""Container for status reports for Xiaomi Mi Smart Pedestal Fan DMaker 1C."""

def __init__(self, data: Dict[str, Any]) -> None:
self.data = data
"""
Response of a Fan1C (dmaker.fan.1c):
{
'id': 1,
'result': [
{'did': 'power', 'siid': 2, 'piid': 1, 'code': 0, 'value': True},
{'did': 'fan_level', 'siid': 2, 'piid': 2, 'code': 0, 'value': 2},
{'did': 'child_lock', 'siid': 3, 'piid': 1, 'code': 0, 'value': False},
{'did': 'swing_mode', 'siid': 2, 'piid': 3, 'code': 0, 'value': False},
{'did': 'power_off_time', 'siid': 2, 'piid': 10, 'code': 0, 'value': 0},
{'did': 'buzzer', 'siid': 2, 'piid': 11, 'code': 0, 'value': False},
{'did': 'light', 'siid': 2, 'piid': 12, 'code': 0, 'value': True},
{'did': 'mode', 'siid': 2, 'piid': 7, 'code': 0, 'value': 0},
],
'exe_time': 280
}
"""

@property
def power(self) -> str:
"""Power state."""
return "on" if self.data["power"] else "off"

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

@property
def mode(self) -> OperationMode:
"""Operation mode."""
return OperationMode[OperationModeMiot(self.data["mode"]).name]

@property
def speed(self) -> int:
"""Speed of the motor."""
return self.data["fan_level"]

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

@property
def delay_off_countdown(self) -> int:
"""Countdown until turning off in minutes."""
return self.data["power_off_time"]

@property
def led(self) -> bool:
"""True if LED is turned on."""
return self.data["light"]

@property
def buzzer(self) -> bool:
"""True if buzzer is turned on."""
return self.data["buzzer"]

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


class FanMiot(MiotDevice):
mapping = MIOT_MAPPING[MODEL_FAN_P10]

Expand Down Expand Up @@ -289,7 +371,7 @@ def set_child_lock(self, lock: bool):
def delay_off(self, minutes: int):
"""Set delay off minutes."""

if minutes < 0:
if minutes < 0 or minutes > 480:
raise FanException("Invalid value for a delayed turn off: %s" % minutes)

return self.set_property("power_off_time", minutes)
Expand All @@ -312,3 +394,124 @@ class FanP10(FanMiot):

class FanP11(FanMiot):
mapping = MIOT_MAPPING[MODEL_FAN_P11]


class Fan1C(MiotDevice):
mapping = MIOT_MAPPING[MODEL_FAN_1C]

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

@command(
default_output=format_output(
"",
"Power: {result.power}\n"
"Operation mode: {result.mode}\n"
"Speed: {result.speed}\n"
"Oscillate: {result.oscillate}\n"
"LED: {result.led}\n"
"Buzzer: {result.buzzer}\n"
"Child lock: {result.child_lock}\n"
"Power-off time: {result.delay_off_countdown}\n",
)
)
def status(self) -> FanStatus1C:
"""Retrieve properties."""
return FanStatus1C(
{
prop["did"]: prop["value"] if prop["code"] == 0 else None
for prop in self.get_properties_for_mapping()
}
)

@command(default_output=format_output("Powering on"))
def on(self):
"""Power on."""
return self.set_property("power", True)

@command(default_output=format_output("Powering off"))
def off(self):
"""Power off."""
return self.set_property("power", False)

@command(
click.argument("mode", type=EnumType(OperationMode)),
default_output=format_output("Setting mode to '{mode.value}'"),
)
def set_mode(self, mode: OperationMode):
"""Set mode."""
return self.set_property("mode", OperationModeMiot[mode.name].value)

@command(
click.argument("speed", type=int),
default_output=format_output("Setting speed to {speed}"),
)
def set_speed(self, speed: int):
"""Set speed."""
if speed not in (1, 2, 3):
raise FanException("Invalid speed: %s" % speed)

return self.set_property("fan_level", speed)

@command(
click.argument("oscillate", type=bool),
default_output=format_output(
lambda oscillate: "Turning on oscillate"
if oscillate
else "Turning off oscillate"
),
)
def set_oscillate(self, oscillate: bool):
"""Set oscillate on/off."""
return self.set_property("swing_mode", oscillate)

@command(
click.argument("led", type=bool),
default_output=format_output(
lambda led: "Turning on LED" if led else "Turning off LED"
),
)
def set_led(self, led: bool):
"""Turn led on/off."""
return self.set_property("light", led)

@command(
click.argument("buzzer", type=bool),
default_output=format_output(
lambda buzzer: "Turning on buzzer" if buzzer else "Turning off buzzer"
),
)
def set_buzzer(self, buzzer: bool):
"""Set buzzer on/off."""
return self.set_property("buzzer", buzzer)

@command(
click.argument("lock", type=bool),
default_output=format_output(
lambda lock: "Turning on child lock" if lock else "Turning off child lock"
),
)
def set_child_lock(self, lock: bool):
"""Set child lock on/off."""
return self.set_property("child_lock", lock)

@command(
click.argument("minutes", type=int),
default_output=format_output("Setting delayed turn off to {minutes} minutes"),
)
def delay_off(self, minutes: int):
"""Set delay off minutes."""

if minutes < 0 or minutes > 480:
raise FanException("Invalid value for a delayed turn off: %s" % minutes)

return self.set_property("power_off_time", minutes)
Loading

0 comments on commit 619f03a

Please sign in to comment.