-
Not sure I understand, do you mean that Bangle could be trained to recognise what I'm typing on a keyboard? And this could be used later when I'm typing on e.g. a table surface, and Bangle connected to a device without external keyboard, like a phone?
At least when I'm touch typing with two hands, my wrists are resting on a surface and sometimes not moving at all, hence any accelerometer readings are infinitesimal and most probably unusable. Using one hand, and one finger, it might work as long as you "calibrate", i.e. know which x and y positions you are starting from. Could be doable even without AI.Or perhaps you mean something else...?
-
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 :-) -
Thx! Somehow it's not replacing my keyboard either :D
Right before Christmas I actually put a Bangle on my ankle and taught it to recognize if I had shoes or socks on my feet when walking. While the accuracy was not great, it was better than random. I guess by tweaking the gesture sensitivity, using other type of shoes, and collecting lots more data, the accuracy would've been better. And no, I don't need AI to tell me what I'm wearing, HI (Human Intelligence) is good enough for that :)
So far Bangle & AI has for me mainly been a solution looking for a problem to solve, but you gotta start somewhere. -
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
andbackspace
, left outW
). 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(); }
-
-
@Gordon
What's the fastest and easiest way to upgrade the firmware from Windows?
The Web IDE says it can't perform upgrades at the moment, so I finally did it through nrfToolbox, not easy for me and my "fat fingers" to fiddle with my phone, and in addition I needed to do as mentioned above, pressReply
100 times to get it through, 1% at a time.
Now, it's one thing to do it once, and for one watch only, but as I have 11 watches (for a workshop in February) I'm simple not going to do it this way for all of them, in worst case several times per year, so that's why I'm looking for a quicker and easier way. -
An anecdotal observation:
The battery in one of my Bangle 2 lasted 27 days until I charged it. It still had 9% left then so might have survived still a day. Pretty much default settings and apps. Bluetooth was on, wake on twitch on, BUT ~90-95% of the time it was lying idle on a table. Rest of the time I used it for gesture related activities and uploading/downloading data, relatively exhaustively. If I’d used it on my wrist all the time, the battery would most probably not have lasted that long. -
-
Ok, partially answering myself.
Using the code from https://github.com/espruino/BangleApps/blob/master/apps/hidkbd/hid-keyboard.js and changing to0x2b
innext = function (cb) { sendHid(0x2b, cb); };
I'm able to simulate TAB by a gesture I've recorded.
But how to simulate SHIFT+TAB? -
With the below skeleton code I'm trying to send a TAB character (or should it be keypress?) instead of the letter A's. The intention is to control a program which is using the TAB-key to navigate forward, and SHIFT-TAB backward.
If I intepreted the reference at https://www.espruino.com/modules/ble_hid_keyboard.js correctly,
"\t"
might be TAB, but have not succeeded in replacing the..KEY.A..
with..KEY."\t"..
or with some other variations.
Can this be done, if so, how?var kb = require("ble_hid_keyboard"); NRF.setServices(undefined, { hid : kb.report }); function btnPressed() { // Send 'a' kb.tap(kb.KEY.A, 0, function() { // Followed by capital 'A' kb.tap(kb.KEY.A, kb.MODIFY.SHIFT); }); } // trigger btnPressed whenever the button is pressed setWatch(btnPressed, BTN, {edge:"rising",repeat:true,debounce:50});
-
So, I have 3 Puck.js (through Kickstarter, so I guess V2) lying around unused. Does anyone have the above mentioned firmware built? I know it's possible to build it myself, but using a Win10 computer it seems it isn't straightforward, and it also seems time consuming, so if anyone one has a ready made firmware I'd be happy to receive it : )
-
-
Well, ask 10 persons and you'll get 20 answers :-)
To me the easiest to read is the one with 2nd biggest one and bolded. Now, this is said without having a clue how small the text will be on the watch display itself. As a side note, on Bangle v1 the date with the default watch face is completely unreadable for me, even with reading glasses, only a magnifying glass can help me read the date.
I'd also appreciate if the text would not be "touching" the frame, is it possible to add a couple of pixels margin? -
-
var model = require("Storage").read("mytfmodel");
var tf = require("tensorflow").create(2048, model);
tf.getInput()[0] = x;
tf.invoke();
print(tf.getOutput()[0]);Hi - Thx, this makes sense now once I saw it, am new to JS! However, it produces the below error. I've uploaded the same tfmodel file used with the gesture functions to the watch and named it
mytfmodel
Does this perhaps mean that the tfmodel file should be converted to a flat string? In that case, can that be done with JS?>Uncaught TypeError: Model is not a Flat String/ArrayBuffer at line 2 col 50 var tf = require("tensorflow").create(2048, model); ^ Uncaught Error: Cannot read property 'getInput' of undefined at line 3 col 3 tf.getInput()[0] = x; ^ Uncaught Error: Cannot read property 'invoke' of undefined at line 4 col 3 tf.invoke(); ^ Uncaught Error: Cannot read property 'getOutput' of undefined at line 5 col 9 print(tf.getOutput()[0]); ^ >
-
Thx for the comments!
As I have a .tfmodel file exported from Edge Impulse, I’d like to use that but I’m not sure how to read it with JS and if it needs conversion before.
The same .tfmodel file works as such with the Bangle gesture functions.@Gordon I guess you are superbusy with the Kickstarter campaign right now. When you get time, could you please shed some light on how to use a
.tfmodel
file created by an external system in Javascript.
I.e., like in the code in post #9 above, how should the .tfmodel-file be "introduced" into the code? As the Bangle gesture functions can use it, I guess there should be a way to use it within JS also? -
-
Hmm, the below code is copied from here and just slightly appended. When running it, Bangle restarts itself, when
tf.invoke()
is run.
What is amiss here, the code, or me?Edit 1: While the code resets the physical watch, it runs fine in the emulator, both for Bangle 1 and 2
Edit 2: Tried to reset the watch by installing default apps, still same problemvar model=atob("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"); var x = 1; var tf = require("tensorflow").create(2048, model); print ("1"); tf.getInput()[0] = x; print ("2"); tf.invoke(); print ("3"); print(tf.getOutput()[0]); print ("4");
-
Hi @user130273, would you be willing to share your code? I've been able to collect data and train a model using Edge Impulse (if interested, see this tutorial I wrote). This is however using the Bangle gesture commands, but now I'd like to learn how to use raw accelerometer and other data.
Especially this part is unclear: when I've uploaded a trained model (.tfmodel + .tfnames) to the watch, how can I run inference code on it?Edit: Aargh, it should be something in line with the below code that I've seen many times... Still interested to see your code though.
var model=atob("......"); var tf = require("tensorflow").create(2048, model); tf.getInput()[0] = x; tf.invoke(); print(tf.getOutput()[0]);
-
-
-
@johan_m_o Thx for your comments!
-
@Gordon, have now created the tutorial using GitHub and made a pull request. Whenever you (or others) have time, take a look at it and try it out, and shoot me some comments!
And you use all fingers, not only index fingers? If using all fingers, how would it be possible to know what finger you pressed a key with, even if you are moving your wrists?