python-miio icon indicating copy to clipboard operation
python-miio copied to clipboard

Xiaomi Air Purifier 3C (zhimi.airpurifier.mb4) can handle one udp packet at once while offline

Open gfduszynski opened this issue 2 years ago • 11 comments

While investigating unreliable behavior of my Air Purifier 3C I've tried to determine if my network is at fault or something else. Eventually I've found out that nodejs client miio is much more reliable at discovering my device. The only difference was that node client sent single packet and python-miio optimistically sends 3 (attached is wireshark log down below). I've confirmed that this is an issue for by editing miioprotocol.py to only send a single packet for handshake.

This improved situation a lot, however device still becomes unavailable from time to time due to the fact that now that single packet must arrive.

Issue is also present when trying to simultaneously trying to set mode to Favorite and setting RPM. Each call sends its own discovery packet and that happens to not work 4 out of 5 times. If one waits for the previous call to finish before doing next all is good.

Version information:

  • OS: Docker/HomeAssistant 2021.10.6
  • python-miio: 0.5.8

Device information:

  • Model: zhimi.airpurifier.mb4
  • Hardware version: esp32
  • Firmware version: 2.0.8

To Reproduce Steps to reproduce the behavior:

  1. Disable internet access to your device (dns: reject, else: drop)
  2. Execute miiocli -d device --ip 192.168.64.143 --token <redacted> info
  3. Device does not respond to any of handshake packets (almost always)

Expected behavior def discover(...) should wait for each udp packet to timeout independently.

Perhaps calls should be queued for execution ?

Wire shark log out-hades.zip 192.168.64.143 - Air Purifier 3C 192.168.64.224 - python-miio 192.168.64.225 - nodejs miio

gfduszynski avatar Oct 23 '21 21:10 gfduszynski

Could you try adding a very short sleep after each try to see if that helps? Could the issue be similar as described in https://github.com/python-kasa/python-kasa/issues/229 (and its linked issue), i.e., related to wifi powersave missing those discovery/handshake packets?

rytilahti avatar Oct 23 '21 21:10 rytilahti

1 second works for the initial discovery, however simultaneous calls still still result in a problem.

I think that the packets are not missed (wifi power saving etc.), since single packet has a higher chance of working than 3. Probably less then perfect firmware quality on that ESP32 :)

EDIT: With 1 sec it still manages to become unavailable from time to time.

gfduszynski avatar Oct 23 '21 22:10 gfduszynski

image

After 2 days of use here is a visualization of the effect that change has made.

Remaining issue is due to OpenWrt configuration probably, as I get: Mon Oct 25 20:31:57 2021 daemon.info hostapd: wlan1-2: STA <mac addr> IEEE 802.11: deauthenticated due to inactivity (timer DEAUTH/REMOVE)

gfduszynski avatar Oct 25 '21 18:10 gfduszynski

For other unfortunate souls who might have this issue: https://openwrt.org/faq/deauthenticated_due_to_inactivity

gfduszynski avatar Oct 25 '21 18:10 gfduszynski

Thanks for the elaborate tests! I'm wondering what'd be the best way to handle this, maybe make the default number of packets device implementation specific?

On the second issue, would you mind adding a note about the deauthenticated issue to the troubleshooting page in the docs?

rytilahti avatar Nov 04 '21 18:11 rytilahti

I'm happy to help :) Firstly I would opt for changing behavior of def discover to be more like def send in that instead of sending n packets lets wait for a response for the first packet (however no longer than around 1 second) and only retry when no reply is received. If it's going to work it will probably work on first or second try. On second try this translates to around ~1s.

Secondly, regarding the allowed packet count. If not for the fact that there is already a ton of code in here I would probably go for queuing of api calls. This would make it reasonably easy to predefine number of calls allowed to be executed simultaneously on per device basis.

I'm no python expert, but since explicit queuing would probably to problematic I would instead opt for resource locking.

So for example when a call is made, ultimately it will have to reach an instance of MiIOProtocol and execute def send, I'm thinking that if on api level method set_whatever would have to obtain exclusive access to this instance (or some other entity), otherwise wait till it's available this will effectively act as a queue but the change may be confined to a smaller area of code.

See this sample code: https://www.bogotobogo.com/python/Multithread/python_multithreading_Synchronization_Lock_Objects_Acquire_Release.php

PS: That issue with OpenWrt is not totally sorted out on my part (I gave my self a little break) I'm worried that there may be something else wrong. I'm planning on resuming debugging sessions on weekend with different access point (different chipset and software) and see if the issue remains.

gfduszynski avatar Nov 04 '21 20:11 gfduszynski

This looks to be better suited: https://docs.python.org/3/library/asyncio-sync.html

This would allow to even confine the change to send method only.

gfduszynski avatar Nov 04 '21 20:11 gfduszynski

I've done some experimenting: https://github.com/gfduszynski/python-miio/commit/d996e9c668c9076e593c1c8cb0971631f5718837

This now works when executing simultaneous calls, like set mode "Favorite" + Speed. It's would probably be better to use asyncio instead of threading library but this is just a proof of concept.

Regarding the OpenWrt issue, I've connected the device to the Ubiquiti AP for now and we will see it it behaves differently.

gfduszynski avatar Nov 07 '21 20:11 gfduszynski

Device seems to be working better with Ubiquity AP. Dropouts occur about every hour or two for minute or two; it's acceptable for this kind of device.

gfduszynski avatar Nov 09 '21 20:11 gfduszynski

@rytilahti Hello there, I haven't heard from you recently. Are you ok ?

gfduszynski avatar Nov 16 '21 18:11 gfduszynski

Maybe my issue is related. My Smartmi Fan 2 can be detected but then immediately becomes unavailable in Home Assistant. Then it sometimes pop backs up randomly allowing for a command or two before going offline again.

It works perfectly reliably in the Mi app and through Homebridge with this plugin so it does seem to be an issue with the python Xiaomi lib. Not sure what to do here.

spacecakes avatar Feb 25 '22 08:02 spacecakes