Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Air Purifier Pro second motor speed #176

Merged
merged 6 commits into from
Jan 27, 2018

Conversation

yawor
Copy link
Contributor

@yawor yawor commented Jan 24, 2018

Air Purifier Pro has a motor2_speed property which actually holds the speed of the main fan motor. The motor1_speed seems to indicate the speed of a small fan which probably sucks air into the laser PM2.5 detector on the back.
Maybe someone with Air Purifier 2 could verify if this property is also available in that model. Depending on the result I'll update the docstrings for both motor speed properties.

@@ -87,6 +88,7 @@ def test_status(self):
assert self.state().filter_hours_used == self.device.start_state["f1_hour_used"]
assert self.state().use_time == self.device.start_state["use_time"]
assert self.state().motor_speed == self.device.start_state["motor1_speed"]
assert self.state().motor2_speed == self.device.start_state["motor2_speed"]

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

line too long (83 > 79 characters)

@yawor yawor changed the title Air purifier motor speed Air Purifier Pro second motor speed Jan 24, 2018
@coveralls
Copy link

coveralls commented Jan 24, 2018

Coverage Status

Coverage increased (+1.2%) to 63.763% when pulling fbb861b on yawor:air_purifier_motor_speed into 744ef07 on rytilahti:master.

@syssi
Copy link
Collaborator

syssi commented Jan 24, 2018

Please be careful. The Air Purifier 2 doesn't provide the new property:

bin/mirobo --ip ip --token token raw_command get_prop '["motor2_speed"]'
Sending cmd get_prop with params ['motor2_speed']
[None]

@yawor
Copy link
Contributor Author

yawor commented Jan 24, 2018

Ok thanks. In that case I've annotated the return value as Optional[int] correctly.
What values do you get on Air Purifier 2 in the motor1_speed property? Does it provide real rpm for the main fan motor?

@yawor
Copy link
Contributor Author

yawor commented Jan 24, 2018

I've done some research and decompiled the MiHome app. I've found some methods and properties that are not yet implemented in the library. The MiHome downloads external code in form of plugins depending on the devices it detect. I only have the Air Purifier and the airpurifier plugin it downloaded contains only code related to Air Purifier and Air Monitor devices.

Properties and related methods I've found in the code:
rfid_product_id, rfid_tag - these are probably connected to the type of the filter installed, as each filter has an RFID tag built-in - the type of the filter is displayed in the app; I have an antibacterial filter installed and rfid_product_id has value 0:0:41:30
sleep_mode, sleep_time, sleep_data_num - I have no idea what that is, in my case they have values: silent, 6879 and 30
app_extra - again, no idea, the value is 0 and can be changed using set_app_extra method which takes a single int. I've tried settings values 1 and 2 and both were accepted and updated the property, but I don't know what this is responsible for.
act_sleep - seems to be related to the learning mode in the MiHome app; there's a method set_act_sleep which takes a single string argument, possible args are: closed (learn mode turned off in MiHome) and single (learn mode turned on)
act_det - according to the code it should be on or off with a related set_act_det method (takes a single argument) but on Air Purifier Pro these are not available
Some other methods:
reset_filter1 - takes no args, but returns error: {'code': -6009, 'message': 'not_support'} on Air Purifier Pro
set_spd_favorite - takes single int argument; I've tried few different values but it always returns error: {'code': -5001, 'message': 'invaild_arg'}

I've also tried brute force some property names. I've found one that is not used in the MiHome app (maybe there are even more but brute force takes a lot of time as it generates all possible letter, digit and underscore combination. I've let it work for around 10-15 minutes but couldn't find anything more, but I've only started with 5-letter long props.

cola - contains 00000000000000000000000000000000 - the length is the same as the token hex string, coincidence? Maybe it has something to do with optional external PM2.5 detector - I don't have one so I can't test this theory. There's a set_cola method which accepts a single string argument. I've tried setting the same value (too scared to try different one :P) and the method returned ok.

@syssi can you verify my findings on Air Purifier 2?

@syssi
Copy link
Collaborator

syssi commented Jan 24, 2018

You did an awesome job! Thanks a lot! I'm very interested in "how to decompile the mihome app". I will checkout your properties as soon as possible (friday).

@syssi
Copy link
Collaborator

syssi commented Jan 24, 2018

Could you please extend both examples by the new property + value (manually):

/~https://github.com/yawor/python-miio/blob/200996ace2f1f6d6269071bfe2ab940acefeb6fa/miio/airpurifier.py#L34-L48

Thanks!

@yawor
Copy link
Contributor Author

yawor commented Jan 24, 2018

Regarding the decompilation, I've first downloaded the MiHome apk (com.xiaomi.mihome.apk) from the Google Play and I've used apk2gold apk decompiler. The results may vary depending on the level and method of optimisation and obfuscation. The resulting Java code is usually not very readable and requires some analysis. First I've tried to look for some known patterns/commands like get_prop, set_volume etc but I couldn't find any in the MiHome itself. Then I've noticed that the app has support for plugins: it downloads separate apk files and run them.
I've checked the MiHome backup from my phone which I made to extract the Air Purifier token. I've extracted whole backup.ab file and I've found apps/com.xiaomi.smarthome/f/plugin/install/mpk/1/370.apk file. After passing it through the apk2gold I've got AirMonitorDevice.java and AirPurifierDevice.java (amongst other support classes).
I've tried to find how and from where the MiHome app downloads these plugins to try and manually download them for other devices but for now I couldn't do it. Maybe it could be done by installing some proxy on the phone or router and capture whole traffic between the app and xiaomi servers. SSL may be a problem if the app checks the certificate validity.

@yawor
Copy link
Contributor Author

yawor commented Jan 24, 2018

OK I did more thorough analysis of the code:
act_det property and set_act_det method are for. This controls if the air quality detector works when the Air Purifier power is off. On Air Purifier Pro it's always active. This property is in the code related to V1, V2 and V3 models.
app_extra property and set_app_extra methods are related to some kind of Turbo mode. Here's a description of it from the app: "Noise level will increase if you switch to Turbo mode. Switch anyway?". I've tried this in different modes (favorite, auto and silent) but didn't see any changes in speed or noise level. This property is in the code related to V6 model but it doesn't work for me.
rfid_product_id and rfid_tag are in the code related to V6 model. rfid_product_id holds the filter type for sure. I don't know if this is a complete and correct list as the code is heavily mangled, but here's what I've been able to deduce:
0:0:41:30 - Antibacterial type
0:0:30:31 or 0:0:00:31 - Anti-Formaldehyde type
0:0:30:33 or 0:0:00:33 - Standard type
I also have a standard filter at home so I can replace it tomorrow and check if this is correct.

@syssi
Copy link
Collaborator

syssi commented Jan 25, 2018

Could you check some additional thing? I'm unsure about the valid operation modes ("set_mode") of the Air Purifier Pro. Could you provide a list of valid modes (auto, silent, favorite, idle) and favorite_levels (0...16) of your device?

@yawor
Copy link
Contributor Author

yawor commented Jan 25, 2018

The Pro model doesn't support the idle mode (I've mentioned this in the #141). Other modes are correct. It seems that it supports favorite_level up to 17 (0..17).
According the to MiHome code, the Turbo mode I've mentioned earlier should enable even level 18, but I've tried that and it doesn't accept level 18 even when app_extra is set to 1.

@syssi
Copy link
Collaborator

syssi commented Jan 25, 2018

$ mirobo raw_command get_prop '["rfid_product_id"]'       
Sending cmd get_prop with params ['rfid_product_id']
[None]
$ mirobo raw_command get_prop '["rfid_tag"]'       
Sending cmd get_prop with params ['rfid_tag']
[None]
$ mirobo raw_command get_prop '["sleep_mode"]'
Sending cmd get_prop with params ['sleep_mode']
['idle']
$ mirobo raw_command get_prop '["sleep_time"]'
Sending cmd get_prop with params ['sleep_time']
[75936]
$ mirobo raw_command get_prop '["sleep_data_num"]'
Sending cmd get_prop with params ['sleep_data_num']
[20]
$ mirobo raw_command get_prop '["act_sleep"]'     
Sending cmd get_prop with params ['act_sleep']
['close']
$ mirobo raw_command set_act_sleep '[]'
Sending cmd set_act_sleep with params []
{'error': {'code': -5001, 'message': 'invaild_arg'}, 'id': 10}
$ mirobo raw_command get_prop '["cola"]'
Sending cmd get_prop with params ['cola']
['00000000000000000000000000000000']
$ mirobo raw_command get_prop '["act_det"]'
Sending cmd get_prop with params ['act_det']
[None]
$ mirobo raw_command get_prop '["app_extra"]'
Sending cmd get_prop with params ['app_extra']
[0]
$ mirobo raw_command set_app_extra '[1]'
Sending cmd set_app_extra with params [1]
['ok']
$ mirobo raw_command set_app_extra '[2]'
Sending cmd set_app_extra with params [2]
['ok']
$ mirobo raw_command set_app_extra '[3]'
Sending cmd set_app_extra with params [3]
['ok']
$ mirobo raw_command set_app_extra '[4]'
Sending cmd set_app_extra with params [4]
['ok']
$ mirobo raw_command get_prop '["app_extra"]'
Sending cmd get_prop with params ['app_extra']
[4]
$ mirobo raw_command set_app_extra '[0]'
Sending cmd set_app_extra with params [0]
['ok']
$ mirobo raw_command get_prop '["app_extra"]'
Sending cmd get_prop with params ['app_extra']
[0]

# Previous filter lifetime
$ mirobo raw_command get_prop '["filter1_life"]'
Sending cmd get_prop with params ['filter1_life']
[70]
$ mirobo raw_command get_prop '["f1_hour_used"]'
Sending cmd get_prop with params ['f1_hour_used']
[1027]
# Filter reset
$ mirobo raw_command reset_filter1 '[]'
Sending cmd reset_filter1 with params []
['ok']
$ mirobo raw_command get_prop '["filter1_life"]'
Sending cmd get_prop with params ['filter1_life']
[100]
$ mirobo raw_command get_prop '["f1_hour_used"]'
Sending cmd get_prop with params ['f1_hour_used']
[0]

Conclusion for the zhimi.airpurifier.m1 (Air Purifier 2):

rfid_product_id, rfid_tag, act_det isn't supported
set_app_extra supports various numbers
act_sleep value is called "close"
reset_filter1 works fine!

@yawor
Copy link
Contributor Author

yawor commented Jan 25, 2018

set_act_sleep should be sent with an argument: close or single. In the app I've also seen 3rd value double but APPro doesn't accept that value. Like I've mentioned in previous post, according to the MiHome code this controls the Learn mode.
Is the PM2.5 sensor in AP2 always active like in APPro (even when power is off)? The act_det property should control that on some models.

@yawor
Copy link
Contributor Author

yawor commented Jan 26, 2018

BTW I've switched to JADX (/~https://github.com/skylot/jadx) for APK decompilation and it creates much better results and thanks to GUI version it's much easier to work with. I'm going to update this PR with more properties.

@syssi
Copy link
Collaborator

syssi commented Jan 26, 2018

$ mirobo raw_command get_prop '["act_sleep"]'
Sending cmd get_prop with params ['act_sleep']
['close']
$ mirobo raw_command set_act_sleep '["single"]'
Sending cmd set_act_sleep with params ['single']
['ok']
$ mirobo raw_command get_prop '["act_sleep"]'
Sending cmd get_prop with params ['act_sleep']
['single']
$ mirobo raw_command set_act_sleep '["double"]'
Sending cmd set_act_sleep with params ['double']
{'error': {'code': -5001, 'message': 'invaild_arg'}, 'id': 29}
$ mirobo raw_command set_act_sleep '["close"]'
Sending cmd set_act_sleep with params ['close']
['ok']
$ mirobo raw_command get_prop '["act_sleep"]'
Sending cmd get_prop with params ['act_sleep']
['close']

@syssi
Copy link
Collaborator

syssi commented Jan 26, 2018

I can provide some more APKs if interested:

hammerhead:/data/data/com.xiaomi.smarthome/files/plugin/install/mpk # find . -name *.apk                                                                                                                                                                                      
./com.xiaomi.catalog/7/7.apk
./com.mijiashop.main/19/19.apk
./com.xiaomi.pinwei/2/2.apk
./108/10707.apk
./108/10585.apk
./229/7956.apk
./229/10301.apk
./107/8653.apk
./473/7490.apk
./503/10146.apk
./503/10536.apk

@yawor
Copy link
Contributor Author

yawor commented Jan 26, 2018

7, 19 and 2 are plugins for built-in shops and catalogs. I would like to take a look at the other ones.

@syssi
Copy link
Collaborator

syssi commented Jan 26, 2018

You've mail!

@@ -95,6 +107,8 @@ def test_status(self):
assert self.state().child_lock == (self.device.start_state["child_lock"] == 'on')
assert self.state().illuminance == self.device.start_state["bright"]
assert self.state().volume == self.device.start_state["volume"]
assert self.state().filter_rfid_product_id == self.device.start_state["rfid_product_id"]
assert self.state().filter_rfid_tag == self.device.start_state["rfid_tag"]

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

line too long (82 > 79 characters)

@@ -95,6 +107,8 @@ def test_status(self):
assert self.state().child_lock == (self.device.start_state["child_lock"] == 'on')
assert self.state().illuminance == self.device.start_state["bright"]
assert self.state().volume == self.device.start_state["volume"]
assert self.state().filter_rfid_product_id == self.device.start_state["rfid_product_id"]

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

line too long (96 > 79 characters)

@syssi syssi merged commit 18d422f into rytilahti:master Jan 27, 2018
@yawor yawor deleted the air_purifier_motor_speed branch January 28, 2018 00:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants