-
• #2
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 intervalI 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
- 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)
-
• #3
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.
-
• #4
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 useBluetooth.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 usingBluetooth.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. -
• #5
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 -
• #6
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!
-
• #7
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.
-
• #8
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 -
• #9
Ok - i made some progress here.
I switched things over to use
Bluetooth.println(...)
andNRF.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!
-
• #10
ios may cache it too but the
NRF.setConnectionInterval(7.5)
is for connection not advertising, for that you need to setinterval:xx
inside NRF.setAdvertising call, default is 375ms -
• #11
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.
I have a hobby project where my puck.js sends accel data:
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.
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.