Skip to content

Commit 619f03a

Browse files
authored
Add basic dmaker.fan.1c support (#1012)
1 parent 9ba6741 commit 619f03a

5 files changed

Lines changed: 344 additions & 24 deletions

File tree

README.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ Supported devices
110110
- Xiaomi Philips Zhirui Bedroom Smart Lamp
111111
- Huayi Huizuo Lamps
112112
- Xiaomi Universal IR Remote Controller (Chuangmi IR)
113-
- Xiaomi Mi Smart Pedestal Fan V2, V3, SA1, ZA1, ZA3, ZA4, P5, P9, P10, P11
113+
- Xiaomi Mi Smart Pedestal Fan V2, V3, SA1, ZA1, ZA3, ZA4, 1C, P5, P9, P10, P11
114114
- Xiaomi Rosou SS4 Ventilator (leshow.fan.ss4)
115115
- Xiaomi Mi Air Humidifier V1, CA1, CA4, CB1, MJJSQ, JSQ, JSQ1, JSQ001
116116
- Xiaomi Mi Water Purifier (Basic support: Turn on & off)

miio/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
from miio.exceptions import DeviceError, DeviceException
3737
from miio.fan import Fan, FanP5, FanSA1, FanV2, FanZA1, FanZA4
3838
from miio.fan_leshow import FanLeshow
39-
from miio.fan_miot import FanMiot, FanP9, FanP10, FanP11
39+
from miio.fan_miot import Fan1C, FanMiot, FanP9, FanP10, FanP11
4040
from miio.gateway import Gateway
4141
from miio.heater import Heater
4242
from miio.heater_miot import HeaterMiot

miio/discovery.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@
8686
MODEL_FAN_ZA3,
8787
MODEL_FAN_ZA4,
8888
)
89-
from .fan_miot import MODEL_FAN_P9, MODEL_FAN_P10, MODEL_FAN_P11
89+
from .fan_miot import MODEL_FAN_1C, MODEL_FAN_P9, MODEL_FAN_P10, MODEL_FAN_P11
9090
from .heater import MODEL_HEATER_MA1, MODEL_HEATER_ZA1
9191
from .powerstrip import MODEL_POWER_STRIP_V1, MODEL_POWER_STRIP_V2
9292
from .toiletlid import MODEL_TOILETLID_V1
@@ -180,6 +180,7 @@
180180
"zhimi-fan-za1": partial(Fan, model=MODEL_FAN_ZA1),
181181
"zhimi-fan-za3": partial(Fan, model=MODEL_FAN_ZA3),
182182
"zhimi-fan-za4": partial(Fan, model=MODEL_FAN_ZA4),
183+
"dmaker-fan-1c": partial(FanMiot, model=MODEL_FAN_1C),
183184
"dmaker-fan-p5": partial(Fan, model=MODEL_FAN_P5),
184185
"dmaker-fan-p9": partial(FanMiot, model=MODEL_FAN_P9),
185186
"dmaker-fan-p10": partial(FanMiot, model=MODEL_FAN_P10),

miio/fan_miot.py

Lines changed: 205 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,20 @@
1010
MODEL_FAN_P9 = "dmaker.fan.p9"
1111
MODEL_FAN_P10 = "dmaker.fan.p10"
1212
MODEL_FAN_P11 = "dmaker.fan.p11"
13+
MODEL_FAN_1C = "dmaker.fan.1c"
1314

1415
MIOT_MAPPING = {
16+
MODEL_FAN_1C: {
17+
# https://miot-spec.org/miot-spec-v2/instance?type=urn:miot-spec-v2:device:fan:0000A005:dmaker-1c:1
18+
"power": {"siid": 2, "piid": 1},
19+
"fan_level": {"siid": 2, "piid": 2},
20+
"child_lock": {"siid": 3, "piid": 1},
21+
"swing_mode": {"siid": 2, "piid": 3},
22+
"power_off_time": {"siid": 2, "piid": 10},
23+
"buzzer": {"siid": 2, "piid": 11},
24+
"light": {"siid": 2, "piid": 12},
25+
"mode": {"siid": 2, "piid": 7},
26+
},
1527
MODEL_FAN_P9: {
1628
# Source https://miot-spec.org/miot-spec-v2/instance?type=urn:miot-spec-v2:device:fan:0000A005:dmaker-p9:1
1729
"power": {"siid": 2, "piid": 1},
@@ -128,7 +140,7 @@ def angle(self) -> int:
128140

129141
@property
130142
def delay_off_countdown(self) -> int:
131-
"""Countdown until turning off in seconds."""
143+
"""Countdown until turning off in minutes."""
132144
return self.data["power_off_time"]
133145

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

149161

162+
class FanStatus1C(DeviceStatus):
163+
"""Container for status reports for Xiaomi Mi Smart Pedestal Fan DMaker 1C."""
164+
165+
def __init__(self, data: Dict[str, Any]) -> None:
166+
self.data = data
167+
"""
168+
Response of a Fan1C (dmaker.fan.1c):
169+
170+
{
171+
'id': 1,
172+
'result': [
173+
{'did': 'power', 'siid': 2, 'piid': 1, 'code': 0, 'value': True},
174+
{'did': 'fan_level', 'siid': 2, 'piid': 2, 'code': 0, 'value': 2},
175+
{'did': 'child_lock', 'siid': 3, 'piid': 1, 'code': 0, 'value': False},
176+
{'did': 'swing_mode', 'siid': 2, 'piid': 3, 'code': 0, 'value': False},
177+
{'did': 'power_off_time', 'siid': 2, 'piid': 10, 'code': 0, 'value': 0},
178+
{'did': 'buzzer', 'siid': 2, 'piid': 11, 'code': 0, 'value': False},
179+
{'did': 'light', 'siid': 2, 'piid': 12, 'code': 0, 'value': True},
180+
{'did': 'mode', 'siid': 2, 'piid': 7, 'code': 0, 'value': 0},
181+
],
182+
'exe_time': 280
183+
}
184+
"""
185+
186+
@property
187+
def power(self) -> str:
188+
"""Power state."""
189+
return "on" if self.data["power"] else "off"
190+
191+
@property
192+
def is_on(self) -> bool:
193+
"""True if device is currently on."""
194+
return self.data["power"]
195+
196+
@property
197+
def mode(self) -> OperationMode:
198+
"""Operation mode."""
199+
return OperationMode[OperationModeMiot(self.data["mode"]).name]
200+
201+
@property
202+
def speed(self) -> int:
203+
"""Speed of the motor."""
204+
return self.data["fan_level"]
205+
206+
@property
207+
def oscillate(self) -> bool:
208+
"""True if oscillation is enabled."""
209+
return self.data["swing_mode"]
210+
211+
@property
212+
def delay_off_countdown(self) -> int:
213+
"""Countdown until turning off in minutes."""
214+
return self.data["power_off_time"]
215+
216+
@property
217+
def led(self) -> bool:
218+
"""True if LED is turned on."""
219+
return self.data["light"]
220+
221+
@property
222+
def buzzer(self) -> bool:
223+
"""True if buzzer is turned on."""
224+
return self.data["buzzer"]
225+
226+
@property
227+
def child_lock(self) -> bool:
228+
"""True if child lock is on."""
229+
return self.data["child_lock"]
230+
231+
150232
class FanMiot(MiotDevice):
151233
mapping = MIOT_MAPPING[MODEL_FAN_P10]
152234

@@ -289,7 +371,7 @@ def set_child_lock(self, lock: bool):
289371
def delay_off(self, minutes: int):
290372
"""Set delay off minutes."""
291373

292-
if minutes < 0:
374+
if minutes < 0 or minutes > 480:
293375
raise FanException("Invalid value for a delayed turn off: %s" % minutes)
294376

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

313395
class FanP11(FanMiot):
314396
mapping = MIOT_MAPPING[MODEL_FAN_P11]
397+
398+
399+
class Fan1C(MiotDevice):
400+
mapping = MIOT_MAPPING[MODEL_FAN_1C]
401+
402+
def __init__(
403+
self,
404+
ip: str = None,
405+
token: str = None,
406+
start_id: int = 0,
407+
debug: int = 0,
408+
lazy_discover: bool = True,
409+
model: str = MODEL_FAN_1C,
410+
) -> None:
411+
super().__init__(ip, token, start_id, debug, lazy_discover)
412+
self.model = model
413+
414+
@command(
415+
default_output=format_output(
416+
"",
417+
"Power: {result.power}\n"
418+
"Operation mode: {result.mode}\n"
419+
"Speed: {result.speed}\n"
420+
"Oscillate: {result.oscillate}\n"
421+
"LED: {result.led}\n"
422+
"Buzzer: {result.buzzer}\n"
423+
"Child lock: {result.child_lock}\n"
424+
"Power-off time: {result.delay_off_countdown}\n",
425+
)
426+
)
427+
def status(self) -> FanStatus1C:
428+
"""Retrieve properties."""
429+
return FanStatus1C(
430+
{
431+
prop["did"]: prop["value"] if prop["code"] == 0 else None
432+
for prop in self.get_properties_for_mapping()
433+
}
434+
)
435+
436+
@command(default_output=format_output("Powering on"))
437+
def on(self):
438+
"""Power on."""
439+
return self.set_property("power", True)
440+
441+
@command(default_output=format_output("Powering off"))
442+
def off(self):
443+
"""Power off."""
444+
return self.set_property("power", False)
445+
446+
@command(
447+
click.argument("mode", type=EnumType(OperationMode)),
448+
default_output=format_output("Setting mode to '{mode.value}'"),
449+
)
450+
def set_mode(self, mode: OperationMode):
451+
"""Set mode."""
452+
return self.set_property("mode", OperationModeMiot[mode.name].value)
453+
454+
@command(
455+
click.argument("speed", type=int),
456+
default_output=format_output("Setting speed to {speed}"),
457+
)
458+
def set_speed(self, speed: int):
459+
"""Set speed."""
460+
if speed not in (1, 2, 3):
461+
raise FanException("Invalid speed: %s" % speed)
462+
463+
return self.set_property("fan_level", speed)
464+
465+
@command(
466+
click.argument("oscillate", type=bool),
467+
default_output=format_output(
468+
lambda oscillate: "Turning on oscillate"
469+
if oscillate
470+
else "Turning off oscillate"
471+
),
472+
)
473+
def set_oscillate(self, oscillate: bool):
474+
"""Set oscillate on/off."""
475+
return self.set_property("swing_mode", oscillate)
476+
477+
@command(
478+
click.argument("led", type=bool),
479+
default_output=format_output(
480+
lambda led: "Turning on LED" if led else "Turning off LED"
481+
),
482+
)
483+
def set_led(self, led: bool):
484+
"""Turn led on/off."""
485+
return self.set_property("light", led)
486+
487+
@command(
488+
click.argument("buzzer", type=bool),
489+
default_output=format_output(
490+
lambda buzzer: "Turning on buzzer" if buzzer else "Turning off buzzer"
491+
),
492+
)
493+
def set_buzzer(self, buzzer: bool):
494+
"""Set buzzer on/off."""
495+
return self.set_property("buzzer", buzzer)
496+
497+
@command(
498+
click.argument("lock", type=bool),
499+
default_output=format_output(
500+
lambda lock: "Turning on child lock" if lock else "Turning off child lock"
501+
),
502+
)
503+
def set_child_lock(self, lock: bool):
504+
"""Set child lock on/off."""
505+
return self.set_property("child_lock", lock)
506+
507+
@command(
508+
click.argument("minutes", type=int),
509+
default_output=format_output("Setting delayed turn off to {minutes} minutes"),
510+
)
511+
def delay_off(self, minutes: int):
512+
"""Set delay off minutes."""
513+
514+
if minutes < 0 or minutes > 480:
515+
raise FanException("Invalid value for a delayed turn off: %s" % minutes)
516+
517+
return self.set_property("power_off_time", minutes)

0 commit comments

Comments
 (0)