Avatar for DrewS

DrewS

Member since Jun 2021 • Last active Mar 2024
  • 2 conversations
  • 10 comments

Most recent activity

  • in Puck.js, Pixl.js and MDBT42
    Avatar for DrewS

    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.

  • in Puck.js, Pixl.js and MDBT42
    Avatar for DrewS

    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(hand­le_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!

  • in Puck.js, Pixl.js and MDBT42
    Avatar for DrewS

    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!

  • in Puck.js, Pixl.js and MDBT42
    Avatar for DrewS

    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.

  • Avatar for DrewS

    Ok, tried the uart:false flag but no luck there. I did look more deeply into the serial-console as well, but (perhaps not surprisingly) it's a bit of a stretch for my technical level. I'll see how far I can get using the BLE keyboard input module instead for now. Thx again for all the help.

  • Avatar for DrewS

    Thanks Gordon - I've been away from my computer but will give these a shot and follow up later this week. Much appreciated!

  • Avatar for DrewS

    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
    
  • Avatar for DrewS

    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
    
Actions