Debugging HID/Keyboard with serial to another Puck?

Posted on
of 2
/ 2
  • I've found that if onInit immediately disconnects, it can confuse the IDE.

    Yes, I can imagine :) However there should be no need to force a disconnect - I know you're trying to weed out potential issues, but in this case it might just be causing problems. I'd just call setServices direct on onInit, and then manually disconnect?

    I'd be worried that after saving, if you then power on the device, 3 seconds is enough that the Mac might already have tried to connect to it, and is then disconnected in the middle of negotiation.

    But yeah, if it works and it's a sleep/wake issue then it could be something else I guess.

  • Another day, another rewrite :)

    I've actually got it working now, although I'm still not sure what I changed. I stripped out pretty much everything, rewrote, and then rigged it so on('disconnect') executes NRF.sleep()... the idea being that when the Puck disconnects (for whatever reason), it won't reconnect until manually woken by a keypress.

    I don't mess with advertising or services beyond the initial setup, which unfortunately loses the battery monitor; however, that doesn't seem to make much difference anyway: the device is either connected (no advertising?) or disconnected and sleeping (no advertising)

    I'm also not sure what the drain on this is. Next I'll have to rig it up to test power consumption while sleeping, as that's the ultimate aim. I also have to solve the auto-switch-off that triggered this whole matter in the first place; I'm hoping it's just a case of setTimeout(...NRF.disconnect()..., 1000*60*10) ... clearTimeout().

    For the benefit of anyone else attempting this -­5522deeabd9d1ef389607e434a :

    var hid = require("ble_hid_keyboard");
    var is_connected = false;
    var ad_values = {};
    var ad_options = { name: "Page-Turn-o-Matic 4000" };
    var sleep_timeout;
    // Object to handle multiple-clicking and long-clicking on
    // a button, using EventEmitter.
    function Btn(btn) {
      var o = this; = [0,'click','double','triple','quadruple'­];
  = function (e) {
        if ( 2.0 < e.time - e.lastTime) {
          if (o.h)
          o.i = o.h = o.l = undefined;
          o.emit('long', 0);
        else {
          // If there's no previous click or it's more than
          // a second ago, it's a new chain of clicks.
          if (!o.i || !o.l || 1.0 < e.time - o.l)
            o.i = 1;
            o.i ++;
          o.l = e.time;
          if (o.h)
          o.h = setTimeout(function () {
            o.h = undefined;
            if ([o.i]) o.emit([o.i], o.i);
            o.i = 0;
          }, 400);
      o.w = setWatch(, btn, { repeat:true, edge:'falling', debounce : 50 });
      return o;
    var btn;
    function blinken (colour, count)
      digitalWrite([LED1,LED2,LED3], colour);
        digitalWrite([LED1,LED2,LED3], 0);
        if (count > 1) {
          setTimeout(function () {
            blinken(colour, --count);
          }, 50);
      }, 25);
    function set_sleep_timeout() {
      if (sleep_timeout)
      sleep_timeout = setTimeout(function () {
      }, 1000*60*5);
    function clear_sleep_timeout() {
      if (sleep_timeout) {
        sleep_timeout = undefined;
    // Connection tracking
    function on_connect () {
      is_connected = true;
    function on_disconnect() {
      is_connected = false;
      NRF.sleep(); // Prevent reconnection until manually woken
    // Button click events
    function on_click (c) {
      if (is_connected) {
        // Single flash - move right
        blinken(0b010, 1);
        hid.tap(hid.KEY.RIGHT, 0);
      else {
    function on_double (c) {
      if (is_connected) {
        // Double flash - move left
        blinken(0b010, 2);
        hid.tap(hid.KEY.LEFT, 0);
      else {
    function on_triple (c) {
      NRF.disconnect(); // on('disconnect') causes NRF.sleep
    function on_quadruple (c) {
    // Initialisation
    function init () {
      NRF.on('connect', on_connect);
      NRF.on('disconnect', on_disconnect);
      NRF.setAdvertising({}, ad_options);
      NRF.setServices(undefined, { hid : });
      btn = new Btn(BTN);
      btn.on('click', on_click);
      btn.on('double', on_double);
      btn.on('triple', on_triple);
      btn.on('quadruple', on_quadruple);
      console.log("Note: to activate, either disconnect manually, or NRF.disconnect();"); 
    E.on('init', init);

    (updated with auto-sleep)

  • Great! The whole disconnect...sleep thing sounds like a great idea!

    Then to automatically turn off, all you have to do is call disconnect - and you wake up when you press the button.

  • @Gordon: it seems like a great idea, but I think I've hit a snag.

    I'm running my current code on 1v92.103 (the nightly the last time I DFU'ed) and rigged it up to my Lidl multimeter (only the best quality kit for me...)

    After startup but not connected, just advertising, it draws about 0.15mA.
    While connected but idle, it draws about 0.35mA.
    While sending data and flashing an LED, it draws about 5mA.

    All fine so far. However, if I force a disconnect with a triple-click which should trigger an NRF.sleep(), it'll draw around 3mA steadily!

    So, when it's purportedly "off" it seems to draw ten times the power of "on". Not good. Hmm. Any thoughts? Any chance you could give it a go? My equipment and skill level makes these figures somewhat less than conclusive.

    (Edit to include photo of my test setup: desoldering braid separated by Post-It note)

    1 Attachment

    • JPEG image-B8B7555BDD80-1.jpeg
  • I've just had a go at this, and measure 0.2mA when connected, 0.03mA when not, 5mA ish when the red LED is on - as you'd expect.

    But then if I simply type NRF.sleep() I measure 0.03mA as if it's advertising - checking on it, it looks like it is still advertising... so there's something really odd going on there.

    If I do this:

    function on_disconnect() {
    NRF.on('disconnect', on_disconnect);

    (including adding a flash) I get the same thing for some reason.

    However if I run your code I do get the 3mA power draw.

    I'd be a bit careful about the readings you get from just a meter (I use a capacitor across the meter to try and reduce any peaks of power draw) but even so, something odd seems to be happening in your case, and I'm not sure why.

  • Hmm - I just did some changes to the advertising as I was including some other libraries that might have been getting in the way, and now simply typing NRF.sleep() can make this happen.

    As far as I can make out, it happens when the device disconnects but doesn't start sending advertising packets again at all.

    So you might be able to work around it by delaying your call to NRF.sleep by a second.

    This works for me setTimeout("NRF.sleep()",1000);NRF.disco­nnect() when NRF.sleep() failed, so you might find:

    function on_disconnect() {
      is_connected = false;
      setTimeout("NRF.sleep()",1000); // Prevent reconnection until manually woken

    works for you?

  • So any thoughts… problem with the firmware, or problem with my code? Or, my code is doing something legal but unexpected, perhaps? Hngh.

    Re: multimeter, good tip! I was thinking that, but was intending instead to hook it up to another Puck (or possibly a Pico or Original) to do voltage monitoring and graph it over time to get some idea of likely battery life. However, with the preliminary check with the multimeter being two or three levels of magnitude more than I was expecting, I figured I'd raise that before I went too far with it.

  • Ah, crossed messages... I'll take a look, thanks.

  • Yes, thanks for spotting it! It's a strange one as for whatever reason I wasn't able to trigger it manually before.

    I've just pushed new code with the extra advertising library removed, so hopefully you'll get better results using the delay. Hopefully other changes I made haven't messed up the bonding/HID stuff :)

  • Ah, okay. I just tried your setTimeout(...,1000) hack (still on 1v92.103) and the LEDs went nuts: it disconnects (purple), sets a timeout for 1 second, reconnects before that timeout occurs (cyan), then sleeps per the timeout (disconnect, purple) and so on, until it happens to not connect before it sleeps! :)

    Thanks for the fix; I'm eager to try it. Have you got the most recent Puck binary, by any chance? I haven't got cross-compilation rigged up at the moment.

  • Maybe try using a shorter delay?

    The latest Travis build should have it in...

  • After scrapping my FTDI232 for now and buying a new USB-to-Serial, I've finally got it talking via serial; Serial1.setConsole(true);, of course. Unfortunately, getting through the back-door just seems to confuse matters a bit :(

    I'm now getting a range of errors, including:

    Uncaught Error: Got BLE error NRF_ERROR_INVALID_STATE
     at line 1 col 11

    on disconnect; the dreaded 13313 appeared once or twice, as well as Uncaught Error: Got BLE error code 15, although I think those were both due to old code being stored and/or general connection weirdness.

    Anyway, I'm going to clear this down and start again. I really appreciate your patience. :)

  • Weird... Error 15 is 'FORBIDDEN'!

    NRF_ERROR_INVALID_STATE is error 8 - I've just been adding text versions of the errors.

    At least you're getting something via serial now - that should hopefully help things along quite a lot. Any luck with the NRF.sleep with a shorter interval on it? Maybe even just 100ms?

  • By the way, just to confuse matters: The UART needs the high speed clock on, which kills power consumption - so the act of actually having the UART connected at startup will affect the power draw all by itself :(

  • 250ms seems to work. I've also eliminated a lot of the problems by encapsulating the entire program within function init() { so variables are local and aren't preserved. Also, by NRF.removeAllListeners(), E.removeAllListeners(), clearWatch().

    When I use serial, it tends to behave a little differently on the BLE side, and since serial will suck the battery dry -- I've used up two CR2032s this afternoon alone! Thank IKEA and Lidl for cheap cells -- it's not much use for the original purpose of testing power consumption. Saying that, the new firmware has seemed to stop the 3mA current draw when sleeping.

    I'm having to unpair/pair the device on the iPad to get it to properly act, sometimes.

    I've updated the Gist above with my current code, if interested. I'm going to try it out properly tonight.

  • Great! Glad it's working!

    Saying that, the new firmware has seemed to stop the 3mA current draw when sleeping.

    The new firmware will still break if you don't have the delay though. I've filed a bug with Nordic in the hope that they'll have some workaround - it does seem to be an issue with the Nordic SDK rather than Espruino itself.

  • Also, 2 batteries in one day?? I'm really surprised.

    They're supposed to be 200mAh batteries at least, and usually even if you're doing a bunch of stuff with the microcontroller it'll only draw 4mA - so you'd hope to get 50 hours out of it.

    If you turned the LEDs on the power draw would go right up, but still - it takes commitment to drain them so quickly :)

  • Hello, what's the latest status on this project Tom & Gordon ?

  • (ie the most up-to-date code / link )

  • Which bit are you interested in?

    As of 2v07 Bluetooth HID is pretty stable now, power usage is right down as well.

  • I’ll have to update too!

    Incidentally, I just noticed this new message on my iPad... must be something in the latest iOS as I haven’t done anything to my Puck for months — if it ain’t broke...

    Any ideas?

    2 Attachments

    • CDA81506-4D26-4CC5-999A-6FB0493D13EC.jpeg
    • 967F6EE5-B872-4007-8EE8-2C521F60EC3C.jpeg
  • My initial thought is that's probably Apple's way of saying "you didn't pay us to be part of our MFi program". At least they didn't go out of their way to actually block it from working.

    But after some googling it seems it even affects some Apple Bluetooth accessories, and Apple's website says "some iPad models":­

    So my guess is that it shares the same radio hardware for WiFi and Bluetooth, so whenever it has to service Bluetooth it has to stop transmitting on WiFi.

    It's possible that if you do NRF.setConnectionInterval(100) on the Puck it'll be fine - maybe it was complaining about Puck.js's default polling interval (before it goes into low power mode after 60 seconds) of 7.5ms - which would mean the iPad was having to stop WiFi comms pretty often.

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

Debugging HID/Keyboard with serial to another Puck?

Posted by Avatar for tom.gidden @tom.gidden