Skip to content

Commit e58ad5e

Browse files
authored
Add support for zhimi.heater.za2 (#1301)
* Add support for zhimi.heater.za2 * Add support to za2 in `heater_miot.py` * Delete `heater_miot_za2.py` * Fix LedBrightness * Improve `LedBrightness` logic. * Update readme; fix some bugs * Unsupported models fall back to mc2 Co-authored-by: PRO <pro@mail.ustc.edu.cn>
1 parent 7daa6e1 commit e58ad5e

2 files changed

Lines changed: 76 additions & 27 deletions

File tree

README.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ Supported devices
146146
- Xiaomi Mi Smart Space Heater
147147
- Xiaomiyoupin Curtain Controller (Wi-Fi) (lumi.curtain.hagl05)
148148
- Xiaomi Xiaomi Mi Smart Space Heater S (zhimi.heater.mc2)
149+
- Xiaomi Xiaomi Mi Smart Space Heater 1S (zhimi.heater.za2)
149150
- Yeelight Dual Control Module (yeelink.switch.sw1)
150151
- Scishare coffee maker (scishare.coffee.s1102)
151152
- Qingping Air Monitor Lite (cgllc.airm.cgdn1)

miio/heater_miot.py

Lines changed: 75 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import enum
22
import logging
3-
from typing import Any, Dict
3+
from typing import Any, Dict, Optional
44

55
import click
66

@@ -9,42 +9,70 @@
99
from .miot_device import DeviceStatus, MiotDevice
1010

1111
_LOGGER = logging.getLogger(__name__)
12-
_MAPPING = {
13-
# Source https://miot-spec.org/miot-spec-v2/instance?type=urn:miot-spec-v2:device:heater:0000A01A:zhimi-mc2:1
14-
# Heater (siid=2)
15-
"power": {"siid": 2, "piid": 1},
16-
"target_temperature": {"siid": 2, "piid": 5},
17-
# Countdown (siid=3)
18-
"countdown_time": {"siid": 3, "piid": 1},
19-
# Environment (siid=4)
20-
"temperature": {"siid": 4, "piid": 7},
21-
# Physical Control Locked (siid=6)
22-
"child_lock": {"siid": 5, "piid": 1},
23-
# Alarm (siid=6)
24-
"buzzer": {"siid": 6, "piid": 1},
25-
# Indicator light (siid=7)
26-
"led_brightness": {"siid": 7, "piid": 3},
12+
_MAPPINGS = {
13+
"zhimi.heater.mc2": {
14+
# Source https://miot-spec.org/miot-spec-v2/instance?type=urn:miot-spec-v2:device:heater:0000A01A:zhimi-mc2:1
15+
# Heater (siid=2)
16+
"power": {"siid": 2, "piid": 1},
17+
"target_temperature": {"siid": 2, "piid": 5},
18+
# Countdown (siid=3)
19+
"countdown_time": {"siid": 3, "piid": 1},
20+
# Environment (siid=4)
21+
"temperature": {"siid": 4, "piid": 7},
22+
# Physical Control Locked (siid=5)
23+
"child_lock": {"siid": 5, "piid": 1},
24+
# Alarm (siid=6)
25+
"buzzer": {"siid": 6, "piid": 1},
26+
# Indicator light (siid=7)
27+
"led_brightness": {"siid": 7, "piid": 3},
28+
},
29+
"zhimi.heater.za2": {
30+
# Source https://miot-spec.org/miot-spec-v2/instance?type=urn:miot-spec-v2:device:heater:0000A01A:zhimi-za2:1
31+
# Heater (siid=2)
32+
"power": {"siid": 2, "piid": 2},
33+
"target_temperature": {"siid": 2, "piid": 6},
34+
# Countdown (siid=4)
35+
"countdown_time": {"siid": 4, "piid": 1},
36+
# Environment (siid=5)
37+
"temperature": {"siid": 5, "piid": 8},
38+
"relative_humidity": {"siid": 5, "piid": 7},
39+
# Physical Control Locked (siid=7)
40+
"child_lock": {"siid": 7, "piid": 1},
41+
# Alarm (siid=3)
42+
"buzzer": {"siid": 3, "piid": 1},
43+
# Indicator light (siid=7)
44+
"led_brightness": {"siid": 6, "piid": 1},
45+
},
2746
}
2847

2948
HEATER_PROPERTIES = {
30-
"temperature_range": (18, 28),
31-
"delay_off_range": (0, 12 * 3600),
49+
"zhimi.heater.mc2": {
50+
"temperature_range": (18, 28),
51+
"delay_off_range": (0, 12 * 3600),
52+
},
53+
"zhimi.heater.za2": {
54+
"temperature_range": (16, 28),
55+
"delay_off_range": (0, 8 * 3600),
56+
},
3257
}
3358

3459

3560
class LedBrightness(enum.Enum):
61+
"""Note that only Xiaomi Smart Space Heater 1S (zhimi.heater.za2) supports `Dim`."""
62+
3663
On = 0
3764
Off = 1
65+
Dim = 2
3866

3967

4068
class HeaterMiotException(DeviceException):
4169
pass
4270

4371

4472
class HeaterMiotStatus(DeviceStatus):
45-
"""Container for status reports from the Xiaomi Smart Space Heater S."""
73+
"""Container for status reports from the Xiaomi Smart Space Heater S and 1S."""
4674

47-
def __init__(self, data: Dict[str, Any]) -> None:
75+
def __init__(self, data: Dict[str, Any], model: str) -> None:
4876
"""
4977
Response (MIoT format) of Xiaomi Smart Space Heater S (zhimi.heater.mc2):
5078
@@ -59,6 +87,7 @@ def __init__(self, data: Dict[str, Any]) -> None:
5987
]
6088
"""
6189
self.data = data
90+
self.model = model
6291

6392
@property
6493
def power(self) -> str:
@@ -85,6 +114,11 @@ def temperature(self) -> float:
85114
"""Current temperature."""
86115
return self.data["temperature"]
87116

117+
@property
118+
def relative_humidity(self) -> Optional[int]:
119+
"""Current relative humidity."""
120+
return self.data.get("relative_humidity")
121+
88122
@property
89123
def child_lock(self) -> bool:
90124
"""True if child lock is on, False otherwise."""
@@ -98,13 +132,17 @@ def buzzer(self) -> bool:
98132
@property
99133
def led_brightness(self) -> LedBrightness:
100134
"""LED indicator brightness."""
101-
return LedBrightness(self.data["led_brightness"])
135+
value = self.data["led_brightness"]
136+
if self.model == "zhimi.heater.za2" and value:
137+
value = 3 - value
138+
return LedBrightness(value)
102139

103140

104141
class HeaterMiot(MiotDevice):
105-
"""Main class representing the Xiaomi Smart Space Heater S (zhimi.heater.mc2)."""
142+
"""Main class representing the Xiaomi Smart Space Heater S (zhimi.heater.mc2) & 1S
143+
(zhimi.heater.za2)."""
106144

107-
mapping = _MAPPING
145+
_mappings = _MAPPINGS
108146

109147
@command(
110148
default_output=format_output(
@@ -125,7 +163,8 @@ def status(self) -> HeaterMiotStatus:
125163
{
126164
prop["did"]: prop["value"] if prop["code"] == 0 else None
127165
for prop in self.get_properties_for_mapping()
128-
}
166+
},
167+
self.model,
129168
)
130169

131170
@command(default_output=format_output("Powering on"))
@@ -146,7 +185,9 @@ def off(self):
146185
)
147186
def set_target_temperature(self, target_temperature: int):
148187
"""Set target_temperature ."""
149-
min_temp, max_temp = HEATER_PROPERTIES["temperature_range"]
188+
min_temp, max_temp = HEATER_PROPERTIES.get(
189+
self.model, {"temperature_range": (18, 28)}
190+
)["temperature_range"]
150191
if target_temperature < min_temp or target_temperature > max_temp:
151192
raise HeaterMiotException(
152193
"Invalid temperature: %s. Must be between %s and %s."
@@ -182,15 +223,22 @@ def set_buzzer(self, buzzer: bool):
182223
)
183224
def set_led_brightness(self, brightness: LedBrightness):
184225
"""Set led brightness."""
185-
return self.set_property("led_brightness", brightness.value)
226+
value = brightness.value
227+
if self.model == "zhimi.heater.za2" and value:
228+
value = 3 - value # Actually 1 means Dim, 2 means Off in za2
229+
elif value == 2:
230+
raise ValueError("Unsupported brightness Dim for model '%s'.", self.model)
231+
return self.set_property("led_brightness", value)
186232

187233
@command(
188234
click.argument("seconds", type=int),
189235
default_output=format_output("Setting delayed turn off to {seconds} seconds"),
190236
)
191237
def set_delay_off(self, seconds: int):
192238
"""Set delay off seconds."""
193-
min_delay, max_delay = HEATER_PROPERTIES["delay_off_range"]
239+
min_delay, max_delay = HEATER_PROPERTIES.get(
240+
self.model, {"delay_off_range": (0, 12 * 3600)}
241+
)["delay_off_range"]
194242
if seconds < min_delay or seconds > max_delay:
195243
raise HeaterMiotException(
196244
"Invalid scheduled turn off: %s. Must be between %s and %s"

0 commit comments

Comments
 (0)