Closed unixko closed 4 years ago
Please add -dd
to your calls to get more output (it'll contain the payloads), but the problem here is that those modes seems to differ from those defined in https://github.com/rytilahti/python-miio/blob/master/miio/airpurifier.py#L19 in https://github.com/rytilahti/python-miio/blob/master/miio/airfresh.py#L18 (being integers instead of strings).
Maybe those mode getters should catch the exceptions are report a new mode called "unknown" (and print out a warning) in case this happens, instead of crashing.
This Air Fresh device has 3 mode ["auto", "sleep", "favourite"]. This payload is from sleep mode. The word "sleep" in payload below will change to "auto", "favourite" following current mode accordingly. Errors from each mode are:
off - ValueError: 0 is not a valid OperationMode auto - ValueError: 97 is not a valid OperationMode sleep - ValueError: 60 is not a valid OperationMode favourite - ValueError: 239 is not a valid OperationMode
C:\Python37\Scripts>miiocli -dd airfresh --ip 192.168.0.2 --token xxx info
INFO:miio.cli:Debug mode active
DEBUG:miio.protocol:Unable to decrypt, returning raw bytes: b''
DEBUG:miio.device:Got a response: Container:
data = Container:
data = b'' (total 0)
value = b'' (total 0)
offset1 = 32
offset2 = 32
length = 0
header = Container:
data = b'!1\x00 \x00\x00\x00\x00\x05d5\x9b\x00\x01\\ ' (total 16)
value = Container:
length = 32
unknown = 0
device_id = b'\x05d5\x9b' (total 4)
ts = 1970-01-02 00:45:20
offset1 = 0
offset2 = 16
length = 16
checksum = b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xf
f' (total 16)
DEBUG:miio.device:Container:
data = Container:
data = b'' (total 0)
value = b'' (total 0)
offset1 = 32
offset2 = 32
length = 0
header = Container:
data = b'!1\x00 \x00\x00\x00\x00\x05d5\x9b\x00\x01\\ ' (total 16)
value = Container:
length = 32
unknown = 0
device_id = b'\x05d5\x9b' (total 4)
ts = 1970-01-02 00:45:20
offset1 = 0
offset2 = 16
length = 16
checksum = b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xf
f' (total 16)
DEBUG:miio.device:Discovered 0564359b with ts: 1970-01-02 00:45:20, token: b'fff
fffffffffffffffffffffffffffff'
DEBUG:miio.device:192.168.0.2:54321 >>: {'id': 1, 'method': 'miIO.info', 'params
': []}
DEBUG:miio.device:send (timeout 5): Container:
data = Container:
data = b'\x1a\xfb\xa3U\x1e\xa6!J+\xce\xfca<\xd6\xb3b'... (truncated, tot
al 48)
value = {'id': 1, 'method': 'miIO.info', 'params': []}
offset1 = 32
offset2 = 80
length = 48
header = Container:
data = b'!1\x00P\x00\x00\x00\x00\x05d5\x9b\x00\x01\\!' (total 16)
value = Container:
length = 80
unknown = 0
device_id = b'\x05d5\x9b' (total 4)
ts = 1970-01-02 00:45:21
offset1 = 0
offset2 = 16
length = 16
checksum = b'\xce}\x17\xfcY2,\x08\xb5\x91\xbd.\xa0ntf' (total 16)
DEBUG:miio.device:recv from 192.168.0.2: Container:
data = Container:
data = b'\x1b\xcf\x9a\xdb\xb5\xfa\x02\x1a\xfd\x04\xeb\xe8\xbeqFr'... (tr
uncated, total 464)
value = {'id': 1, 'result': {'life': 89121, 'uid': 1713659356, 'model':
'dmaker.airfresh.t2017', 'token': 'xxx', 'fw_ver':
'2.0.3', 'mcu_fw_ver': '0014', 'miio_ver': '0.0.3', 'hw_ver': 'esp32', 'mmfree':
69152, 'mac': 'AA:BB:CC:DD:EE:FF', 'wifi_fw_ver': 'v3.1.3-8-gce4d3fe10', 'ap':
{'rssi': -65, 'ssid': 'APName', 'primary': 1, 'bssid': 'FF:EE:DD:CC:BB:AA'}, 'ne
tif': {'localIp': '192.168.0.2', 'mask': '255.255.255.0', 'gw': '192.168.0.1'},
'miio_times': [89120, 7, 213, 88900]}}
offset1 = 32
offset2 = 496
length = 464
header = Container:
data = b'!1\x01\xf0\x00\x00\x00\x00\x05d5\x9b\x00\x01\\!' (total 16)
value = Container:
length = 496
unknown = 0
device_id = b'\x05d5\x9b' (total 4)
ts = 1970-01-02 00:45:21
offset1 = 0
offset2 = 16
length = 16
checksum = b'w\x8b|D\xdeMpJ\xb0\xac\x01\xba\xea:\xab\x10' (total 16)
DEBUG:miio.device:192.168.0.2:54321 (ts: 1970-01-02 00:45:21, id: 1) << {'id': 1
, 'result': {'life': 89121, 'uid': 1713659356, 'model': 'dmaker.airfresh.t2017',
'token': 'xxx', 'fw_ver': '2.0.3', 'mcu_fw_ver': '
0014', 'miio_ver': '0.0.3', 'hw_ver': 'esp32', 'mmfree': 69152, 'mac': 'AA:BB:CC
:DD:EE:FF', 'wifi_fw_ver': 'v3.1.3-8-gce4d3fe10', 'ap': {'rssi': -65, 'ssid': 'A
PName', 'primary': 1, 'bssid': 'FF:EE:DD:CC:BB:AA'}, 'netif': {'localIp': '192.1
68.0.2', 'mask': '255.255.255.0', 'gw': '192.168.0.1'}, 'miio_times': [89120, 7,
213, 88900]}}
Model: dmaker.airfresh.t2017
Hardware version: esp32
Firmware version: 2.0.3
Network: {'localIp': '192.168.0.2', 'mask': '255.255.255.0', 'gw': '192.168.0.1'
}
AP: {'rssi': -65, 'ssid': 'APName', 'primary': 1, 'bssid': 'FF:EE:DD:CC:BB:AA'}
C:\Python37\Scripts>miiocli -dd airfresh --ip 192.168.0.2 --token xxx status
INFO:miio.cli:Debug mode active
DEBUG:miio.protocol:Unable to decrypt, returning raw bytes: b''
DEBUG:miio.device:Got a response: Container:
data = Container:
data = b'' (total 0)
value = b'' (total 0)
offset1 = 32
offset2 = 32
length = 0
header = Container:
data = b'!1\x00 \x00\x00\x00\x00\x05d5\x9b\x00\x01b\x88' (total 16)
value = Container:
length = 32
unknown = 0
device_id = b'\x05d5\x9b' (total 4)
ts = 1970-01-02 01:12:40
offset1 = 0
offset2 = 16
length = 16
checksum = b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xf
f' (total 16)
DEBUG:miio.device:Container:
data = Container:
data = b'' (total 0)
value = b'' (total 0)
offset1 = 32
offset2 = 32
length = 0
header = Container:
data = b'!1\x00 \x00\x00\x00\x00\x05d5\x9b\x00\x01b\x88' (total 16)
value = Container:
length = 32
unknown = 0
device_id = b'\x05d5\x9b' (total 4)
ts = 1970-01-02 01:12:40
offset1 = 0
offset2 = 16
length = 16
checksum = b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xf
f' (total 16)
DEBUG:miio.device:Discovered 0564359b with ts: 1970-01-02 01:12:40, token: b'fff
fffffffffffffffffffffffffffff'
DEBUG:miio.device:192.168.0.2:54321 >>: {'id': 1, 'method': 'get_prop', 'params'
: ['power', 'temp_dec', 'aqi', 'average_aqi', 'co2', 'buzzer', 'child_lock', 'hu
midity', 'led_level', 'mode', 'motor1_speed', 'use_time', 'ntcT', 'app_extra', '
f1_hour_used']}
DEBUG:miio.device:send (timeout 5): Container:
data = Container:
data = b'\x1a\xfb\xa3U\x1e\xa6!J+\xce\xfca<\xd6\xb3b'... (truncated, tot
al 224)
value = {'id': 1, 'method': 'get_prop', 'params': ['power', 'temp_dec',
'aqi', 'average_aqi', 'co2', 'buzzer', 'child_lock', 'humidity', 'led_level', 'm
ode', 'motor1_speed', 'use_time', 'ntcT', 'app_extra', 'f1_hour_used']}
offset1 = 32
offset2 = 256
length = 224
header = Container:
data = b'!1\x01\x00\x00\x00\x00\x00\x05d5\x9b\x00\x01b\x89' (total 16)
value = Container:
length = 256
unknown = 0
device_id = b'\x05d5\x9b' (total 4)
ts = 1970-01-02 01:12:41
offset1 = 0
offset2 = 16
length = 16
checksum = b'\xae_\xab\xb1\x8f\xdd\xdb\xf6\xd0T\xb10\x06\xe6\xd0j' (total 16
)
DEBUG:miio.device:recv from 192.168.0.2: Container:
data = Container:
data = b'\x1b\xcf\x9a\xdb\xb5\xfa\x02\x1a\xfd\x04\xeb\xe8\xbeqFr'... (tr
uncated, total 32)
value = {'id': 1, 'result': [True]}
offset1 = 32
offset2 = 64
length = 32
header = Container:
data = b'!1\x00@\x00\x00\x00\x00\x05d5\x9b\x00\x01b\x89' (total 16)
value = Container:
length = 64
unknown = 0
device_id = b'\x05d5\x9b' (total 4)
ts = 1970-01-02 01:12:41
offset1 = 0
offset2 = 16
length = 16
checksum = b'\x03\x9b\x1b\x93o\x0bp\x92\xedP\x95\x93W\xf6(\xfc' (total 16)
DEBUG:miio.device:192.168.0.2:54321 (ts: 1970-01-02 01:12:41, id: 1) << {'id': 1
, 'result': [True]}
DEBUG:miio.device:192.168.0.2:54321 >>: {'id': 2, 'method': 'get_prop', 'params'
: ['filter_life', 'f_hour', 'favorite_level', 'led']}
DEBUG:miio.device:send (timeout 5): Container:
data = Container:
data = b'\xcbL\xc4\x05&`\xba\xac\x830F\xae\n{v\x15'... (truncated, total
96)
value = {'id': 2, 'method': 'get_prop', 'params': ['filter_life', 'f_hou
r', 'favorite_level', 'led']}
offset1 = 32
offset2 = 128
length = 96
header = Container:
data = b'!1\x00\x80\x00\x00\x00\x00\x05d5\x9b\x00\x01b\x8a' (total 16)
value = Container:
length = 128
unknown = 0
device_id = b'\x05d5\x9b' (total 4)
ts = 1970-01-02 01:12:42
offset1 = 0
offset2 = 16
length = 16
checksum = b'qM\x05%\x15\xab{j\xe4>\xe5<\xbf\xa5+\xf9' (total 16)
DEBUG:miio.device:recv from 192.168.0.2: Container:
data = Container:
data = b'\xecX\xf9\x17\xce\xf4\x053\xdd\xee\xdc\x05\xe4\x7f6\x8b'... (tr
uncated, total 112)
value = {'id': 2, 'result': [19, 595, 31, 239, 92, 82, 96, 172, 60, True
, 'sleep', True, 'medium', False, False, True, True, 'forward']}
offset1 = 32
offset2 = 144
length = 112
header = Container:
data = b'!1\x00\x90\x00\x00\x00\x00\x05d5\x9b\x00\x01b\x89' (total 16)
value = Container:
length = 144
unknown = 0
device_id = b'\x05d5\x9b' (total 4)
ts = 1970-01-02 01:12:41
offset1 = 0
offset2 = 16
length = 16
checksum = b'\xbd_\xb7[\xb5\xfc\xc4\x9e\x9b\x1db\\\xc1\xdak&' (total 16)
DEBUG:miio.device:192.168.0.2:54321 (ts: 1970-01-02 01:12:41, id: 2) << {'id': 2
, 'result': [19, 595, 31, 239, 92, 82, 96, 172, 60, True, 'sleep', True, 'medium
', False, False, True, True, 'forward']}
ValueError: 60 is not a valid OperationMode
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "c:\python37\lib\runpy.py", line 193, in _run_module_as_main
"__main__", mod_spec)
File "c:\python37\lib\runpy.py", line 85, in _run_code
exec(code, run_globals)
File "C:\Python37\Scripts\miiocli.exe\__main__.py", line 9, in <module>
File "c:\python37\lib\site-packages\miio\cli.py", line 43, in create_cli
return cli(auto_envvar_prefix="MIIO")
File "c:\python37\lib\site-packages\miio\click_common.py", line 54, in __call_
_
return self.main(*args, **kwargs)
File "c:\python37\lib\site-packages\click\core.py", line 717, in main
rv = self.invoke(ctx)
File "c:\python37\lib\site-packages\click\core.py", line 1137, in invoke
return _process_result(sub_ctx.command.invoke(sub_ctx))
File "c:\python37\lib\site-packages\click\core.py", line 1137, in invoke
return _process_result(sub_ctx.command.invoke(sub_ctx))
File "c:\python37\lib\site-packages\click\core.py", line 956, in invoke
return ctx.invoke(self.callback, **ctx.params)
File "c:\python37\lib\site-packages\click\core.py", line 555, in invoke
return callback(*args, **kwargs)
File "c:\python37\lib\site-packages\miio\click_common.py", line 267, in wrap
result_msg = result_msg_fmt.format(**kwargs)
File "c:\python37\lib\site-packages\miio\airfresh.py", line 81, in mode
return OperationMode(self.data["mode"])
File "c:\python37\lib\enum.py", line 310, in __call__
return cls.__new__(cls, value)
File "c:\python37\lib\enum.py", line 564, in __new__
raise exc
File "c:\python37\lib\enum.py", line 548, in __new__
result = cls._missing_(value)
File "c:\python37\lib\enum.py", line 577, in _missing_
raise ValueError("%r is not a valid %s" % (value, cls.__name__))
ValueError: 60 is not a valid OperationMode
I found that OperationMode value is fan speed. If I set mode to auto, OperationMode will continuously change following current speed. Range is 60 to 300. 0 - off 60 - sleep mode or lowest setting in favourite mode 300 - highest setting in favourite mode
public static final String PROP_CHILD_LOCK = "child_lock";
public static final String PROP_CO2 = "co2";
public static final String PROP_CONTROL_SPEED = "control_speed";
public static final String PROP_DISPLAY = "display";
public static final String PROP_FILTER_HIGH = "filter_efficient";
public static final String PROP_FILTER_MEDIUM = "filter_intermediate";
public static final String PROP_HIGH_DAY = "filter_effi_day";
public static final String PROP_INCREASE_SPEED = "increase_speed";
public static final String PROP_MEDIUM_DAY = "filter_inter_day";
public static final String PROP_MODE = "mode";
public static final String PROP_PM25 = "pm25";
public static final String PROP_POWER = "power";
public static final String PROP_PTC_LEVEL = "ptc_level";
public static final String PROP_PTC_ON = "ptc_on";
public static final String PROP_PTC_STATUS = "ptc_status";
public static final String PROP_SCREEN_DIRECTION = "screen_direction";
public static final String PROP_SOUND = "sound";
public static final String PROP_SPEED = "favourite_speed";
public static final String PROP_TEMP = "temperature_outside";
public static final String TYPE_ALL = "all";
public static final String TYPE_CO2 = "co2";
public static final String TYPE_DEVICE_OFFLINE = "dev_offline";
public static final String TYPE_EFFICIENT = "efficient";
public static final String TYPE_INTERMEDIATE = "intermediate";
public static final String TYPE_PM25 = "pm25";
static {
HOME_PROPERTIES = new String[]{TYPE_PM25, TYPE_CO2, PROP_TEMP, PROP_SPEED, PROP_FILTER_MEDIUM, PROP_MEDIUM_DAY, PROP_FILTER_HIGH, PROP_HIGH_DAY, PROP_CONTROL_SPEED, PROP_POWER, PROP_MODE, PROP_PTC_ON, PROP_PTC_LEVEL, PROP_PTC_STATUS, PROP_CHILD_LOCK, PROP_SOUND, PROP_DISPLAY, PROP_SCREEN_DIRECTION, PROP_INCREASE_SPEED};
}
public boolean getChildLock() {
return TypeUtil.getDefaultBoolean(TypeUtil.toBoolean(this.mPropertiesMap.get(PROP_CHILD_LOCK)), false);
}
public boolean getSound() {
return TypeUtil.getDefaultBoolean(TypeUtil.toBoolean(this.mPropertiesMap.get(PROP_SOUND)), false);
}
public boolean getDisplay() {
return TypeUtil.getDefaultBoolean(TypeUtil.toBoolean(this.mPropertiesMap.get(PROP_DISPLAY)), false);
}
public String getScreenDirection() {
return TypeUtil.toString(this.mPropertiesMap.get(PROP_SCREEN_DIRECTION));
}
public boolean getIncreaseSpeed() {
return TypeUtil.getDefaultBoolean(TypeUtil.toBoolean(this.mPropertiesMap.get(PROP_INCREASE_SPEED)), false);
}
public Integer getSpeed() {
return TypeUtil.toInteger(this.mPropertiesMap.get(PROP_SPEED));
}
public Integer getHighFilter() {
return TypeUtil.toInteger(this.mPropertiesMap.get(PROP_FILTER_HIGH));
}
public Integer getHighDay() {
return TypeUtil.toInteger(this.mPropertiesMap.get(PROP_HIGH_DAY));
}
public Integer getMediumFilter() {
return TypeUtil.toInteger(this.mPropertiesMap.get(PROP_FILTER_MEDIUM));
}
public Integer getMediumDay() {
return TypeUtil.toInteger(this.mPropertiesMap.get(PROP_MEDIUM_DAY));
}
public String getMode() {
return TypeUtil.toString(this.mPropertiesMap.get(PROP_MODE));
}
public Integer getPm25() {
return TypeUtil.toInteger(this.mPropertiesMap.get(TYPE_PM25));
}
public boolean getPower() {
return TypeUtil.getDefaultBoolean(TypeUtil.toBoolean(this.mPropertiesMap.get(PROP_POWER)), false);
}
public boolean getPtcOn() {
return TypeUtil.getDefaultBoolean(TypeUtil.toBoolean(this.mPropertiesMap.get(PROP_PTC_ON)), false);
}
public String getPtcLevel() {
return TypeUtil.toString(this.mPropertiesMap.get(PROP_PTC_LEVEL));
}
public boolean getPtcStatus() {
return TypeUtil.getDefaultBoolean(TypeUtil.toBoolean(this.mPropertiesMap.get(PROP_PTC_STATUS)), false);
}
public Integer getCo2() {
return TypeUtil.toInteger(this.mPropertiesMap.get(TYPE_CO2));
}
public Float getTemp() {
return TypeUtil.toFloat(this.mPropertiesMap.get(PROP_TEMP));
}
public void getSensor(Callback<JSONArray> callback) {
callMethod("get_sensor", callback, new Object[0]);
}
public void setChildLock(boolean state, Callback<JSONArray> callback) {
callMethod("set_child_lock", callback, Boolean.valueOf(state));
}
public void setSound(boolean sound, Callback<JSONArray> callback) {
callMethod("set_sound", callback, Boolean.valueOf(sound));
}
public void setDisplay(boolean display, Callback<JSONArray> callback) {
callMethod("set_display", callback, Boolean.valueOf(display));
}
public void setScreenDirection(String direction, Callback<JSONArray> callback) {
callMethod("set_screen_direction", callback, direction);
}
public void setIncreaseSpeed(boolean increase, Callback<JSONArray> callback) {
callMethod("set_increase_speed", callback, Boolean.valueOf(increase));
}
public void setSpeed(int speed, Callback<JSONArray> callback) {
callMethod("set_favourite_speed", callback, Integer.valueOf(speed));
}
public void setPtcLevel(String level, Callback<JSONArray> callback) {
callMethod("set_ptc_level", callback, level);
}
public void setRoomArea(int area, Callback<JSONArray> callback) {
callMethod("set_room_area", callback, Integer.valueOf(area));
}
public void setPower(boolean power, Callback<JSONArray> callback) {
callMethod("set_power", callback, Boolean.valueOf(power));
}
public void setPtcOn(boolean ptc, Callback<JSONArray> callback) {
callMethod("set_ptc_on", callback, Boolean.valueOf(ptc));
}
public void setWorkMode(String mode, Callback<JSONArray> callback) {
callMethod("set_mode", callback, mode);
}
public void getTimers(Callback<JSONArray> callback) {
callMethod("get_timer", callback, new Object[0]);
}
public void setTimer(String timer, Callback<JSONArray> callback) {
callMethod("set_timer", callback, timer);
}
public void deleteTimer(int timer, Callback<JSONArray> callback) {
callMethod("delete_timer", callback, Integer.valueOf(timer));
}
public void resetFilter(String type, Callback<JSONArray> callback) {
callMethod("set_filter_reset", callback, type);
}
public void setPtcTimer(String timer, Callback<JSONArray> callback) {
callMethod("set_ptc_timer", callback, timer);
}
public void getPtcTimer(Callback<JSONArray> callback) {
callMethod("get_ptc_timer", callback, new Object[0]);
}
I'm quite far away from python (javascript) but i would love to help integrating that device to Home Assistant, where can i start with help? As of my tries with modification of original plugin, there is no PM25 sensor in original XiaomiAirfresh, so we should add first to python-miio and then create integration in home assistant plugin?
One vote more. Do you need any help?
Got it! https://bbs.hassbian.com/thread-8218-1-1.html This works!
@lazzzrus, could you please create a PR? I have no idea how to use that data
@lazzzrus, could you please create a PR? I have no idea how to use that data
Sorry, i don't know how to create PR. Shortly zxytddd (guy from China) created his own custom component for HA. You need to registrate to download the file so i shared it: https://drive.google.com/file/d/1nHdZjWCIhwYH9FNdKqSAZkrdmg_vvASg/view
The config: fan:
So now i can turn the device on/off.
I'm newbie so don't know how to use parameters for dashboard and automation. So would be glad for any help or link to tutorial.
Someone already put @zxytddd code on github at https://github.com/mypal/mi-airfresh so we can use it in this mean time. Anyway native support from python-miio and Home Assistant is surely preferred.
Yes. This integration is far too basic. I can't find the way to control speed and heat level. Is it possible?
# Properties
[
"pm25",
"co2",
"temperature_outside",
"favourite_speed",
"filter_intermediate",
"filter_inter_day",
"filter_efficient",
"filter_effi_day",
"control_speed",
"power",
"mode",
"ptc_on",
"ptc_level",
"ptc_status",
"child_lock",
"sound",
"display",
"screen_direction"
]
# Methods:
get_prop [propArr]
get_ptc_timer []
get_sensor [propArr]
get_timer null
set_child_lock [value]
set_display [value]
set_favourite_speed [areaNum]
set_filter_reset ['efficient']
set_filter_reset ['intermediate']
set_mode [type]
set_power [status]
set_ptc_level [ptc_leavel]
set_ptc_on [ptc_leavel]
set_ptc_timer, [value]
set_screen_direction [direct]
set_sound [value]
Could somebody provide the output of:
miiocli device --ip IP --token TOKEN raw_command get_prop "['pm25', 'co2', 'temperature_outside', 'favourite_speed', 'filter_intermediate', 'filter_inter_day', 'filter_efficient', 'filter_effi_day', 'control_speed', 'power', 'mode', 'ptc_on', 'ptc_level', 'ptc_status', 'child_lock', 'sound', 'display', 'screen_direction']"
@syssi here is the output grabbed from Mi Home app via tcpdump:
-> request = {"id":4219,"method":"get_prop","params":["pm25","co2","temperature_outside","favourite_speed","filter_intermediate","filter_inter_day","filter_efficient","filter_effi_day","control_speed","power","mode","ptc_on","ptc_level","ptc_status","child_lock","sound","display","screen_direction"]}
<- response = {"id":4219,"result":[0,445,3,206,83,74,92,165,60,true,"sleep",false,"high",false,false,false,false,"forward"]}
P.S. I've created pull request for openHAB to add support for this device: https://github.com/openhab/openhab2-addons/pull/6558/files It might be helpful to you.
Please upvote feature request here https://community.home-assistant.io/t/support-for-xiaomi-miio-support-for-dmaker-airfresh-t2017/235798 if you want it integrated in home assistant.
Product Link: https://www.xiaomiyoupin.com/detail?gid=104643