• Just an update to this for other AI & ML nerds like me. With Bangle.js2 I'm now able to spell the English alphabet in the air by "drawing" characters in the air (CAPS only + space and backspace, left out W). If you want to see it in action, check a short video I uploaded to my LinkedIn account, the link should work for anyone.

    The main changes I've done compared to the tutorial I wrote, is putting below code snippet in the beginning of the programs used to collect the gestures and to recognise them. The code sets the sensitivity for starting and ending the gestures. Depending on your use case, you might want to tweak the settings accordingly.

    Bangle.setOptions({gestureEndThresh: Math.pow(700, 2), gestureStartThresh: Math.pow(600,2), gestureInactiveCount: 6, gestureMinLength: 15});
    
    

    If you want to connect your Bangle to e.g. a computer, and write in any application, the below code is what I uploaded to Bangle. I'm sure it could be optimized and cleaned (and commented!), but as a proof of concept, it works good enough for me. Feel free to provide improvements though, that way I'll learn JS better myself.

    var storage = require('Storage');
    var kb = require("ble_hid_keyboard");
    NRF.setServices(undefined, { hid : kb.report });
    
    const settings = storage.readJSON('setting.json',1) || { HID: false };
    
    var sendHid, next, prev, toggle, up, down, profile;
    var time_on_screen = 500;
    
    Bangle.setOptions({gestureEndThresh: Math.pow(700, 2), gestureStartThresh: Math.pow(700,2), gestureInactiveCount: 6, gestureMinLength: 15});
    
    
    if (settings.HID=="kb" || settings.HID=="kbmedia") {
      profile = 'Keyboard';
      if (settings.HID=="kbmedia") {
        sendHid = function (code, cb) {
          try {
            NRF.sendHIDReport([2,0,0,code,0,0,0,0,0]­, () => {
              NRF.sendHIDReport([2,0,0,0,0,0,0,0,0], () => {
                if (cb) cb();
              });
            });
          } catch(e) {
            print(e);
          }
        };
      } else {
        sendHid = function (code, cb) {
          try {
            NRF.sendHIDReport([0,0,code,0,0,0,0,0], () => {
              NRF.sendHIDReport([0,0,0,0,0,0,0,0], () => {
                if (cb) cb();
              });
            });
          } catch(e) {
            print(e);
          }
        };
      }
      next = function (cb) { sendHid(0x4f, cb); };
      prev = function (cb) { sendHid(0x50, cb); };
      toggle = function (cb) { sendHid(0x2c, cb); };
      up = function (cb) {sendHid(0x52, cb); };
      down = function (cb) { sendHid(0x51, cb); };
    } else {
      E.showPrompt("Enable HID?",{title:"HID disabled"}).then(function(enable) {
        if (enable) {
          settings.HID = "kb";
          require("Storage").write('setting.json',­ settings);
          setTimeout(load, 1000, "hidkbd.app.js");
        } else setTimeout(load, 1000);
      });
    }
    
    function drawApp() {
      g.clear();
      g.setFont("6x8",2);
      g.setFontAlign(0,0);
      g.drawString(profile, 120, 120);
      const d = g.getWidth() - 18;
    
      function c(a) {
        return {
          width: 8,
          height: a.length,
          bpp: 1,
          buffer: (new Uint8Array(a)).buffer
        };
      }
    
      g.drawImage(c([16,56,124,254,16,16,16,16­]),d,40);
      g.drawImage(c([16,16,16,16,254,124,56,16­]),d,194);
      g.drawImage(c([0,8,12,14,255,14,12,8]),d­,116);
    }
    
    if (next) {
      Bangle.on('aiGesture', (v) => {
        E.showMessage(v);
        switch (v) {
          case 'A':
            kb.tap(kb.KEY.A, 0);
    //        next(() => {});
            break;
          case 'B':
            kb.tap(kb.KEY.B, 0);
    //        next(() => {});
            break;
          case 'BACKSPACE':
            kb.tap(kb.KEY.BACKSPACE, 0);
            break;
          case 'C':
            kb.tap(kb.KEY.C, 0);
    //        prev(() => {});
            break;
          case 'D':
            kb.tap(kb.KEY.D, 0);
            break;
          case 'E':
            kb.tap(kb.KEY.E, 0);
            break;
          case 'F':
            kb.tap(kb.KEY.F, 0);
            break;
          case 'G':
            kb.tap(kb.KEY.G, 0);
            break;
          case 'H':
            kb.tap(kb.KEY.H, 0);
            break;
          case 'I':
            kb.tap(kb.KEY.I, 0);
            break;
          case 'J':
            kb.tap(kb.KEY.J, 0);
            break;
          case 'K':
            kb.tap(kb.KEY.K, 0);
            break;
          case 'L':
            kb.tap(kb.KEY.L, 0);
            break;
          case 'M':
            kb.tap(kb.KEY.M, 0);
            break;
          case 'N':
            kb.tap(kb.KEY.N, 0);
            break;
          case 'O':
            kb.tap(kb.KEY.O, 0);
            break;
          case 'P':
            kb.tap(kb.KEY.P, 0);
            break;
          case 'Q':
            kb.tap(kb.KEY.Q, 0);
            break;
          case 'R':
            kb.tap(kb.KEY.R, 0);
            break;
          case 'S':
            kb.tap(kb.KEY.S, 0);
            break;
          case 'SPACE':
            kb.tap(kb.KEY[" "], 2);
            break;
          case 'T':
            kb.tap(kb.KEY.T, 0);
            break;
          case 'U':
            kb.tap(kb.KEY.U, 0);
            break;
          case 'V':
            kb.tap(kb.KEY.V, 0);
            break;
          case 'X':
            kb.tap(kb.KEY.X, 0);
            break;
          case 'Y':
            kb.tap(kb.KEY.Y, 0);
            break;
          case 'Z':
            kb.tap(kb.KEY.Z, 0);
            break;
        }
        setTimeout(drawApp, time_on_screen);
      });
    
      setWatch(function(e) {
        var len = e.time - e.lastTime;
        if (len > 0.3 && len < 0.9) {
          E.showMessage('prev');
          setTimeout(drawApp, 1000);
          prev(() => {});
        } else {
          E.showMessage('up');
          setTimeout(drawApp, 1000);
          up(() => {});
        }
      }, BTN1, { edge:"falling",repeat:true,debounce:50})­;
    
      setWatch(function(e) {
        var len = e.time - e.lastTime;
        if (len > 0.3 && len < 0.9) {
          E.showMessage('next');
          setTimeout(drawApp, 1000);
          next(() => {});
        } else {
          E.showMessage('down');
          setTimeout(drawApp, 1000);
          down(() => {});
        }
      }, BTN3, { edge:"falling",repeat:true,debounce:50})­;
    
      setWatch(function(e) {
        E.showMessage('toggle');
        setTimeout(drawApp, 1000);
        toggle();
      }, BTN2, { edge:"falling",repeat:true,debounce:50})­;
    
      drawApp();
    }
    
  • Wow! Looks amazing!
    And the video demonstration turned out to be very epic, I watched it several times and I want more :)

  • Thx @Serj !
    Well, let's see what's next, no promises, what I have in mind will in worst case not include any Bangle or Puck, unless I can sneak them in somehow in the equation. Not revealing anything more for now, don't have a clue if I even can get the human interface and tech to work together :-)

  • You wanted more :-)

    While I've not been able (or even tried) to add any Espruino device to what I've been working on, this was published yesterday. In the video

    and tutorial I'm showing and explaining how to use an EEG-headset and ML (Machine Learning) to control a very simplistic Pong-game. Next related projects are already under work, so if interested, stay tuned :-)

About

Avatar for Serj @Serj started