11import enum
22import logging
3- from typing import Any , Dict
3+ from typing import Any , Dict , Optional
44
55import click
66
99from .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
2948HEATER_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
3560class 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
4068class HeaterMiotException (DeviceException ):
4169 pass
4270
4371
4472class 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
104141class 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