h2zero / NimBLE-Arduino

A fork of the NimBLE library structured for compilation with Arduino, for use with ESP32, nRF5x.
https://h2zero.github.io/NimBLE-Arduino/
Apache License 2.0
670 stars 138 forks source link

Getting BLE_HS_ENOMEM errors when sending data in loop #614

Open jhmaloney opened 7 months ago

jhmaloney commented 7 months ago

I'm a NimBLE newbie.

I'm getting errors when doing repeated setValue/notify to transmit data. (This in the context of implementing the Nordic UART protocol).

Here's the loop that writes the data. It seems to run out of memory after eight iterations.

void writeTest(int count) { const char s = "1234567890123456789\n"; for (int i = 0; i < count; i++) { pTxCharacteristic->setValue((uint8_t ) s, strlen(s)); pTxCharacteristic->notify(); delay(30); } }

What am I missing? Should the code be waiting for one notify to complete before starting the next one? Should it be calling yield()?

I am running this on a micro:bit V2 and it is sending data to a Python3 program (based on the Bleak library) running on a MacBook Pro. I see similar behavior with the Bluefruit Connect app.

I'm hoping to achieve a throughput of at least 2k to 5k bytes/sec. The other direction -- sending data from the computer to the micro:bit -- works great! I get throughputs of over 10k bytes/sec.

Thanks!

h2zero commented 7 months ago

Some logs would help but I suspect that the delay is a bit too short, do you know what the connection interval is?

jhmaloney commented 7 months ago

The connection interval (desc->conn_itvl) is 24 units, which I believe is 30 msecs. Does that mean I should make my delay between notify's a bit over 30 msecs to avoid using up all the buffer? Is there a way to force a more frequent connection interval?

jhmaloney commented 7 months ago

I added a print to the onStatus callback. It looks like the first 8 notify's are okay but it starts to fail after that:

onStatus TX status: 1 rc: 0 onStatus TX status: 1 rc: 0 onStatus TX status: 1 rc: 0 onStatus TX status: 1 rc: 0 onStatus TX status: 1 rc: 0 onStatus TX status: 1 rc: 0 onStatus TX status: 1 rc: 0 onStatus TX status: 1 rc: 0 onStatus TX status: 4 rc: 6 onStatus TX status: 4 rc: 6 onStatus TX status: 4 rc: 6 onStatus TX status: 4 rc: 6 ...

I increased the delay to a full second (1000 msecs) and it still sends only the first eight.

jhmaloney commented 7 months ago

I found my problem.

I was calling writeTest() in response to a request coming from the client. Thus, it was called from the onWrite() callback in my subclass of BLECharacteristicCallbacks. I'm guessing that the BLE stack holds a lock until the onWrite() callback returned. Thus, NimBLE was queuing notify messages to be sent when onWrite() returned, but when writeTest() did too many notify() calls it ran out of buffers.

The fix was to have onWrite() set a flag to request that writeTest() be called from the loop() function. With that change, writeTest() can send thousands of bytes. I'm seeing throughputs of about 5k bytes/second from a micro:bit V2 to my MacBook Pro, which is great!

I now have a new problem.

I'm using a delay in the writeTest() loop to avoid queuing data faster than NimBLE can send it. To maximize bandwidth, I want to minimize that delay. Through trial-and-error, I've found that 12 msecs works but 10 msecs results in errors (status: 4, return code: 6) and lost data. I'd rather not depend on a delay for this.

Is there a function I can call to be sure that a buffer is available for doing a setValue and notify on an attribute? Alternatively, is there some way to get an immediate return code indicating that the setValue or notify failed and should be tried again later? (I'm using onStatus now, but I'm not sure exactly when that gets called. Is it triggered by the setValue() or the notify() call? Is onStatus() called immediately when the error occurs?

Thanks!

h2zero commented 3 weeks ago

@jhmaloney You can request better connection parameters, down to 7.5ms. The only callback is the onStatus which indicates when the notification has been accepted, not sure if that is when it's okay to send though, would need to test.