BLE connection closes after a few minutes

Posted on
  • I have a hobby project where my puck.js sends accel data:

    Puck.on("accel", function (a) {
      var accel = a.acc;
      accel.name = "acc";
      var gyro = a.gyro;
      gyro.name = "gyro";
      var magnitude = {}
      magnitude.name = "mag";
    
      console.log(JSON.stringify(accel));
      console.log(JSON.stringify(gyro));
    
      magnitude.value = Math.sqrt(accel.x * accel.x + accel.y * accel.y + accel.z * accel.z);
      console.log(JSON.stringify(magnitude));
    

    Then I have a python script that uses bleak to connect to the puck, and listen for that console output, which I then pass along to another toy program.

        print ("Connecting to puck " + puck_device.address)
        async with BleakClient(puck_device.address)  as client:
            await client.start_notify(UUID_NORDIC_RX, uart_data_received)
            await asyncio.sleep(100000000.0)  # Keep the connection alive
        await ws_server.wait_closed()
    

    This all works fine for a while, but i've noticed that after a few minutes the speed of the accel data i'm getting starts to slow down, and eventually the connection drops.

    Any suggestions on what might be happening? I looked into trying to do this by purely advertising the IMU data and not making an actual two way connection, but it doesn't look like I get a very high frequency signal that way. Just a few updates per second, and I need closer to 10 updates per second.

    Thanks! p.s. i'm new to BLE so might be doing this a bit backwards.

  • every console.log may trigger new BLE packet(s), typical BLE packet is 21 bytes, if your device running python can negotiate higher MTU then puck can send 50 bytes in one packet. How fast packets can be sent depends on connection interval https://www.espruino.com/Reference#l_NRF_setConnectionInterval and how many packets in one connection interval can devices handle, some can do more than one, some can do only one packet per connection interval

    I would try to

    • use only one console.log - possibly more packets can be send in one interval or more data together can be sent in less packets (may or may not make a difference)
    • minimize data (just array of numbers instead of complex json structures) - less packets
    • make connection interval shorter (beware that smaller values drains battery more)

    if it starts to slow down it can be issue on both sides, on puck side the data may be generated faster than can be sent or some idle mode kicks in. Ther is such mode for BLE connection (see setConnectionInterval linked above) but if data is sent all the time it should not be triggered, not sure if there is something for accelerometer too - possibly not, you can just set sensible rate here https://www.espruino.com/Reference#l_Puck_accelOn

    BTW if the flow of data is steady and relatively fast the connection is indeed better than simple advertising

  • One more comment - If it gets stuck and disconnects I would guess the most probable case is that data is generated faster then it can be sent via BLE.

  • Hi! Using the bluetooth UART to send data like you're doing is usually pretty good as it can buffer up writes and send them at once, but as @fanoush says you need to be careful that you actually can send the data faster than you're sending it.

    I'd suggest:

    • NRF.setConnectionInterval(7.5) as @fanoush says - this should be the default usually, but if you don't specify it and the device is idle for a while it'll fall into a low power state where it only sends every 200ms (until you start sending lots of data again).
    • Don't use console.log() but instead use Bluetooth.println(...) and send all data in one call. console.log worries about getting the formatting right for the REPL (for example displaying the > prompt on each new line) and by using Bluetooth.println you can skip that and save a few chars
    • As @fanoush says you could look at making sure you're sending the data in a slightly more compact way if it's possible

    But the slowing down and disconnecting is a bit of a surprise - there is actually a possibility that you just have a flat battery? Going 'full tilt' reading the accelerometer and sending data can draw a lot more power than just being idle (~10mA vs 0.03mA). It'd last for ~20 hours on a fresh battery but even so if that battery is old it may well end up flattening, then rebooting after?

    For advertising if you did go that way, you can specify NRF.setAdvertising({}, {interval:20}) which is the max of 20ms = 50 times a second, so it should still work for you without a connection (the default is 375ms) - so that could be an option too.

  • how many packets in one connection interval can devices handle

    just checked and we are sending only one packet per connection interval (?) here
    https://github.com/espruino/Espruino/blob/master/targets/nrf5x/bluetooth.c#L981
    there is a warning in comment above it, however it would be interesting to have the value tweakable, maybe most of the time in cases like this increasing the value would help and people can verify in their specific case how high they can go without breaking it

  • Thanks so much for this great feedback. There's a lot for me to try here, but real quick on the battery - that seems to be good. It's at 75%.

    I'll start trying these wonderful suggestions and report back. Thx again for the super quick in depth responses!

  • You're welcome. If it is still not enough then you may also add your own characteristic. since you already use custom code like

    await client.start_notify(UUID_NORDIC_RX, uart_data_received)
    

    then keeping standard console away from this and using same start_notify code just with your own one instead of UUID_NORDIC_RX (or even one of standardized ones for this) could make it a faster and you'd have it more under control as then from accelerometer event you would simply update the characteristics value directly with no nordic uart buffering or limitations.

  • just checked and we are sending only one packet per connection interval

    That's interesting! Thanks for the spot, I'd totally forgotten about that - maybe it was something that was pulled out for Nordic's gateway, when other stuff can handle it fine.

    I could maybe add a E.setFlags flag for it for now - downloads from bangle.js would probably benefit greatly with it

  • Ok - i made some progress here.

    I switched things over to use Bluetooth.println(...) and NRF.setConnectionInterval(7.5). I also consolidated all the logs into a single call.

    But then I started looking into ble advertising because for my use case, one direction communication is fine and if I could get 20 ms between accelerometer updates that would be awesome. I found some sample code in the puck.js documentation, and modified it like so:

    var presses = 0;
    NRF.setConnectionInterval(7.5)
    
    NRF.setAdvertising({},{name: "Puck", discoverable: true, manufacturer: 0x0590, manufacturerData:[presses]});
    
    function updateAdvertising() {
      //print (E.getTemperature())
      //print (E.getBattery())
      presses++;
      NRF.setAdvertising({},{manufacturer: 0x0590, manufacturerData:[presses]});
    }
    
    // Update advertising now
    updateAdvertising();
    // Update advertising every 20 miliseconds...
    setInterval(updateAdvertising, 1);
    
    setInterval(()=>presses++, 1);
    

    Yes, those are intentionally very fast intervals. I was just banging my head against it trying to get it to update quickly.

    on macOS, here is my python script:

    import asyncio
    import time
    from bleak import BleakScanner
    
    last_update_time = None
    
    def handle_advertisement(device, advertisement_data):
        global last_update_time
    
        current_time = time.time()
        if last_update_time is not None:
            time_since_last_update = current_time - last_update_time
            delta_time_str = f", Delta Time: {time_since_last_update:.2f} seconds"
        else:
            delta_time_str = ""
        last_update_time = current_time
    
        if advertisement_data.local_name == "Puck":
            print(advertisement_data.local_name)
            manufacturer_data = advertisement_data.manufacturer_data
            if 1424 in manufacturer_data:  
                data = manufacturer_data[1424]
                data_str = ''.join(format(x, '02x') for x in data)
                print(f"Manufacturer Data: {data_str}{delta_time_str}")  
    
    async def main():
        scanner = BleakScanner()
        scanner.register_detection_callback(handle_advertisement)
        await scanner.start()
        try:
            while True:
                await asyncio.sleep(0.1)  # Reduced sleep time for potentially more frequent updates
        except KeyboardInterrupt:
            await scanner.stop()
    
    if __name__ == "__main__":
        asyncio.run(main())
    

    This doesn't correctly parse my "presses" int, but for now i'm just looking at how fast it updates. This is what my script prints out:

    Puck
    Manufacturer Data: a4, Delta Time: 0.16 seconds
    Puck
    Manufacturer Data: a6, Delta Time: 0.00 seconds
    Puck
    Manufacturer Data: b6, Delta Time: 0.01 seconds
    Puck
    Manufacturer Data: b6, Delta Time: 0.28 seconds
    Puck
    Manufacturer Data: b8, Delta Time: 0.00 seconds
    Puck
    Manufacturer Data: c0, Delta Time: 0.01 seconds
    Puck
    Manufacturer Data: c6, Delta Time: 0.01 seconds
    Puck
    Manufacturer Data: c8, Delta Time: 0.00 seconds
    Puck
    Manufacturer Data: cc, Delta Time: 0.01 seconds
    Puck
    Manufacturer Data: c6, Delta Time: 0.30 seconds
    Puck
    Manufacturer Data: cc, Delta Time: 0.01 seconds
    Puck
    Manufacturer Data: ce, Delta Time: 0.00 seconds
    Puck
    Manufacturer Data: d0, Delta Time: 0.00 seconds
    Puck
    Manufacturer Data: f0, Delta Time: 0.00 seconds
    

    Ok - so that's mostly good. But here's the weird part. In mac OS terminal, I visually see the updates coming in roughly once a second, even though delta time is a fraction of that.

    Sooooo now i'm wondering if my mac BLE service is just buffering stuff up or something? If this is a dead end, I can go back to a direct blue tooth connection which definitely seems to be much faster. But wow it'd be cool if i could do this with advertising. :). Thanks!

  • ios may cache it too but the NRF.setConnectionInterval(7.5) is for connection not advertising, for that you need to set interval:xx inside NRF.setAdvertising call, default is 375ms

  • Ahh - ok so I had two bugs.

    The first was leaving off the interval:xx, and the second was that my delta time was based on the time between any bluetooth updates, not just the puck updates. That was dumb on my part.

    So now my puck js code:

    var presses = 0;
    NRF.setConnectionInterval(7.5)
    
    NRF.setAdvertising({},{name: "Puck", discoverable: true, manufacturer: 0x0590, manufacturerData:[presses], interval:20});
    
    function updateAdvertising() {
      presses++;
      NRF.setAdvertising({},{manufacturer: 0x0590, manufacturerData:[presses], interval:20});
    }
    
    updateAdvertising();
    
    setInterval(updateAdvertising, 1);
    

    and fixing my dumb python bug, gives me this output:

    Puck
    Manufacturer Data: 12, Delta Time: 0.01 seconds
    Puck
    Manufacturer Data: 15, Delta Time: 0.01 seconds
    Puck
    Manufacturer Data: 78, Delta Time: 0.87 seconds
    Puck
    Manufacturer Data: 82, Delta Time: 0.02 seconds
    Puck
    Manufacturer Data: 3c, Delta Time: 0.90 seconds
    Puck
    Manufacturer Data: 63, Delta Time: 0.07 seconds
    Puck
    Manufacturer Data: 68, Delta Time: 0.01 seconds
    Puck
    Manufacturer Data: 23, Delta Time: 0.89 seconds
    Puck
    Manufacturer Data: 24, Delta Time: 0.00 seconds
    Puck
    Manufacturer Data: d8, Delta Time: 0.90 seconds
    Puck
    Manufacturer Data: db, Delta Time: 0.01 seconds
    Puck
    Manufacturer Data: dc, Delta Time: 0.00 seconds
    Puck
    Manufacturer Data: 88, Delta Time: 0.88 seconds
    Puck
    Manufacturer Data: 89, Delta Time: 0.00 seconds
    Puck
    Manufacturer Data: 48, Delta Time: 0.91 seconds
    Puck
    

    And those numbers actually match what I see in terminal. I think at this point I'll pursue a direct connection instead of advertising (but if you have any other suggestions for that I'll happily give them a shot). Thx again for all the quick responses.

  • Post a reply
    • Bold
    • Italics
    • Link
    • Image
    • List
    • Quote
    • code
    • Preview
About

BLE connection closes after a few minutes

Posted by Avatar for DrewS @DrewS

Actions