Most recent activity
-
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!
-
-
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.
-
-
-
Awesome tips -thx! Responses:
Is OpenEmu running on the same Mac you're developing with?
yup!
Maybe try NRF.sendHIDReport([buttons, x, y], () => { inside a try { ... } catch...
Done
I'd remove the print statements.
Ahh - didn't realize that could cause issues. Removed.
You could add a little LED flash in your code so you can see if update is still executing correctly
Done - great suggestion. I have it flashing blue when it starts the sendHID function, red when NRF.sendHIDReport() is successful, and green when it's sending a button press.
With those changes it does expose something weird. When I connect via the IDE, I can flash the device and see both the red and blue LEDs blinking. When I disconnect from the IDE and only connect via the mac bluetooth menu, I only ever see the blue led blinking. So NRF.sendHIDReport() never seems to be successful, unless I connect via the IDE...
Does that add up to anything obviously incorrect?
//https://github.com/espruino/BangleApps/blob/master/apps/boot/hid_info.txt
var joystick_report = new Uint8Array([
0x05, 0x01, // Usage Page (Generic Desktop)
0x09, 0x04, // Usage (Joystick)
0xA1, 0x01, // Collection (Application)
0x09, 0x01, // Usage (Pointer)
0xA1, 0x00, // Collection (Physical)
// Buttons
0x05, 0x09, // Usage Page (Buttons)
0x19, 0x01, // Usage Minimum (1)
0x29, 0x05, // Usage Maximum (5)
0x15, 0x00, // Logical Minimum (0)
0x25, 0x01, // Logical Maximum (1)
0x95, 0x05, // Report Count (5)
0x75, 0x01, // Report Size (1)
0x81, 0x02, // Input (Data, Variable, Absolute)
// padding bits
0x95, 0x03, // Report Count (3)
0x75, 0x01, // Report Size (1)
0x81, 0x03, // Input (Constant)
// Stick
0x05, 0x01, // Usage Page (Generic Desktop)
0x09, 0x30, // Usage (X)
0x09, 0x31, // Usage (Y)
0x15, 0x81, // Logical Minimum (-127)
0x25, 0x7f, // Logical Maximum (127)
0x75, 0x08, // Report Size (8)
0x95, 0x02, // Report Count (2)
0x81, 0x02, // Input (Data, Variable, Absolute)
0xC0, // End Collection (Physical)
0xC0 // End Collection (Application)
]);
NRF.setServices(undefined, { hid : joystick_report });
Puck.accelOn(); // default is 12.5Hz, with gyro
var sendInProgress = false; // Only send one message at a time, do not flood
const sendHid = function (x, y, btn1, btn2, btn3, btn4, btn5, cb) {
try {
const buttons = (btn5<<4) | (btn4<<3) | (btn3<<2) | (btn2<<1) | (btn1<<0);
if (!sendInProgress) {
sendInProgress = true;
try {
NRF.sendHIDReport([buttons, x, y], () =>
{
sendInProgress = false;
//print ([buttons, x, y]);
digitalPulse(LED1,1,1);
if (buttons == 1) {
digitalPulse(LED2,1,1);
}
}
);
} catch(e) {
print(e);
}
}
} catch(e) {
print(e);
}
};
function update() {
const btn1 = BTN1.read();
const accel = Puck.accel();
var x = accel.acc.x/128000;
var y = accel.acc.y/128000;
// rescale to approximately [-127:127]
x *= 10;
y *= 10;
// I assume the joystick requires ints
x = Math.floor( x * 127 );
y = Math.floor( y * 127);
// check limits
if (x > 127) x = 127;
else if (x < -127) x = -127;
if (y > 127) y = 127;
else if (y < -127) y = -127;
sendHid(x & 0xff, y & 0xff, btn1, false, false, false, false);
//print ("x", x, "y", y, "btn1", btn1);
}
setInterval(update, 100); // 10 Hz
-
Ok - I have some incremental progress. Below is my modified js for the puck. I'm testing it in OpenEmu on my Mac. The puck shows up as a joystick (sometimes as 'unknown' and sometimes as 'Puck.js 80b6'). Once I got Y axis analog input triggering, but no buttons and nothing on the x axis.
I also still get the message "BLE Connected, queueing BLE restart for later" in the IDE, but I seem to always get that even when using the BLE Keyboard HID. I've pretty aggressively been disconnecting the puck from bluetooth after flashing it with my program, so I think the bluetooth stack is automatically restarting in HID mode (which seems to be reflected in the device showing up in OpenEMU's joystick list). That said, my workflow could still be wrong here.
Anyway, let me know if you spot anything obviously incorrect. Thx again!
//https://github.com/espruino/BangleApps/blob/master/apps/boot/hid_info.txt
var joystick_report = new Uint8Array([
0x05, 0x01, // Usage Page (Generic Desktop)
0x09, 0x04, // Usage (Joystick)
0xA1, 0x01, // Collection (Application)
0x09, 0x01, // Usage (Pointer)
0xA1, 0x00, // Collection (Physical)
// Buttons
0x05, 0x09, // Usage Page (Buttons)
0x19, 0x01, // Usage Minimum (1)
0x29, 0x05, // Usage Maximum (5)
0x15, 0x00, // Logical Minimum (0)
0x25, 0x01, // Logical Maximum (1)
0x95, 0x05, // Report Count (5)
0x75, 0x01, // Report Size (1)
0x81, 0x02, // Input (Data, Variable, Absolute)
// padding bits
0x95, 0x03, // Report Count (3)
0x75, 0x01, // Report Size (1)
0x81, 0x03, // Input (Constant)
// Stick
0x05, 0x01, // Usage Page (Generic Desktop)
0x09, 0x30, // Usage (X)
0x09, 0x31, // Usage (Y)
0x15, 0x81, // Logical Minimum (-127)
0x25, 0x7f, // Logical Maximum (127)
0x75, 0x08, // Report Size (8)
0x95, 0x02, // Report Count (2)
0x81, 0x02, // Input (Data, Variable, Absolute)
0xC0, // End Collection (Physical)
0xC0 // End Collection (Application)
]);
NRF.setServices(undefined, { hid : joystick_report });
Puck.accelOn(); // default is 12.5Hz, with gyro
var sendInProgress = false; // Only send one message at a time, do not flood
const sendHid = function (x, y, btn1, btn2, btn3, btn4, btn5, cb) {
try {
const buttons = (btn5<<4) | (btn4<<3) | (btn3<<2) | (btn2<<1) | (btn1<<0);
if (!sendInProgress) {
sendInProgress = true;
print ([buttons, x, y]);
NRF.sendHIDReport([buttons, x, y], () => {
sendInProgress = false;
if (cb) cb();
});
}
} catch(e) {
print(e);
}
};
function update() {
const btn1 = BTN1.read();
const accel = Puck.accel();
var x = accel.acc.x/128000;
var y = accel.acc.y/128000;
// rescale to approximately [-127:127]
x *= 10;
y *= 10;
// I assume the joystick requires ints
x = Math.floor( x * 127 );
y = Math.floor( y * 127);
// check limits
if (x > 127) x = 127;
else if (x < -127) x = -127;
if (y > 127) y = 127;
else if (y < -127) y = -127;
sendHid(x & 0xff, y & 0xff, btn1, false, false, false, false);
//print ("x", x, "y", y, "btn1", btn1);
}
setInterval(update, 100); // 10 Hz
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:
and fixing my dumb python bug, gives me this output:
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.