Anyone try using the puck as a USB HID Joystick?

Posted on
  • I'm loving the BLE Keyboard HID input that's supported by the puck (this one), but I'd like to use analog joystick input along with the various button presses.

    Has anyone tried making a joystick HID input module?

  • Yes, I believe it has been implemented for bangle.js, just not as a module. See:

    https://github.com/espruino/BangleApps/b­lob/master/apps/boot/hid_info.txt

    And it gets used like this:

    https://github.com/espruino/BangleApps/b­lob/master/apps/hidjoystick/app.js#L11

    So you should just be able to piece those two bits together - let me know if you need a hand though!

  • Amazing!!!!

    I'll give it a shot and report back. Thx!

  • 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
    
  • Ok, great! Is OpenEmu running on the same Mac you're developing with?

    sometimes as 'unknown' and sometimes as 'Puck.js 80b6'

    I think this is because there's not enough space in advertising for the HID information and the name. There are ways around this but probably you see 'Puck.js 80b6' just because the Mac remembered the name from before.

    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

    Yes, that sounds perfect.

    Only thing I'd say is:

    • Maybe try NRF.sendHIDReport([buttons, x, y], () => { inside a try { ... } catch... - it could fail if you're sending too many HID reports. When the Puck first connects the connection won't be running that quick until it negotiates it up, and that might cause an error which then stops the interval from running. You could add a little LED flash in your code so you can see if update is still executing correctly
    • I'd remove the print statements. The Puck still has the UART service so it'll be trying to send data over it to the Mac (which I guess won't be listening!). That could cause some issues?

    Hope that helps!

  • 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
    
  • Hmm - that is odd - I can't see anything obvious.

    You could try NRF.setServices(undefined, { hid : joystick_report , uart:false }); - this will disable the Bluetooth console (so the IDE won't work at all until you reboot without loading the code) but it might help?

    I'm not sure how technical you are, but the easy way to debug is to attach a USB-TTL converter as in http://www.espruino.com/Puck.js#serial-c­onsole so you can see what's going on even when you are connected by Bluetooth

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

  • 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.

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

Anyone try using the puck as a USB HID Joystick?

Posted by Avatar for DrewS @DrewS

Actions