micropython / micropython-lib

Core Python libraries ported to MicroPython
Other
2.41k stars 998 forks source link

aioble peripheral may need throttling to avoid random disconnects #596

Open PBrunot opened 1 year ago

PBrunot commented 1 year ago

I am using AIOBLE (latest version as per December 2022) on ESP32-C3 with Micropython 1.19 (LOLIN D32 board). My app uses 3 BT LE services (peripheral role),:

I observed frequent client disconnects to client (I tested with another ESP32 and with NRFConnect app on Android). The problem seems to happen more frequently when many requests were done in a rapid succession. In such conditions, the peripheral stopped responding, and disconnected the clients after a while (10-20s). Once the clients reconnected, everything worked again.

I was able to solve the problem after one week of testing, by imposing a minimum delay between all characteristics notifications (in my case 50 ms) : now I don't have any disconnects and everything is stable.

Concept code:

            since_last = time.ticks_ms() - __last_notification_ms
            while _MIN_BLUETOOTH_DELAY_MS > since_last > 0 and not force:
                await asyncio.sleep_ms(_MIN_BLUETOOTH_DELAY_MS - since_last)
                # Need to recheck because another transmission may have happened during await period
                since_last = time.ticks_ms() - __last_notification_ms
            data = __get_notification_data()
            __status_characteristic.write(data, True)
            __last_notification_ms = time.ticks_ms()

I guess there is a minimum delay for physical transmission over bluetooth and handling by RTOS and if we push too many notifications in a short period, something goes wrong. I wrote this issue for informing other AIOBLE users and for suggesting defining a rate limit inside the aioble library, or a warning, because this was quite difficult to troubleshoot.

jimmo commented 1 year ago

Thanks @PBrunot

It would be worth getting to the root cause of why this causes disconnects, but yes this seems to be becoming a bit of a common gotcha. It might be possible to make the server-side char.write and char.notify detect this case and raise a "busy" exception. (i.e. it should be impossible to write Python code that causes a failure like this).

FWIW, using an L2CAP connection-oriented-channel for streaming data is a much better alternative to "streaming" notifications, because flow control is built-in.

andrewleech commented 1 year ago

Is L2CAP available on esp32?

jimmo commented 1 year ago

Is L2CAP available on esp32?

Ah, good point. Now that we've moved to synchronous events on the ESP32 there should be nothing blocking enabling it though, just needs testing.

andrewleech commented 1 year ago

Awesome, yeah I thought you'd done some work in this area recently :-D

RantyDave commented 8 months ago

I'm also getting these frequent (after 5-10 minutes) disconnections. I'm pushing a reasonable quantity of traffic, maybe 50-100 notifications/sec over 7 characteristics, using aioble, and receive an OSError exception (22 EINVAL). Throttling makes no difference. After some quick perusal of the code I see some references to a ring buffer - could it be that it's not dealing with this filling up correctly?