-
• #2
Just for future reference, what did I do wrong when uploading my code in my previous post?
-
• #3
Sun 2019.03.24
'what did I do wrong when uploading my code in my previous post?'
Maybe nothing at all. Formatting of code inside Html form TextArea element can be a bit tricky for the browser parser. There have been a few hiccups I've seen.
Thank you for being pro-active and including your snippet. It might be that the three trailing back-ticks need to be on a separate line, or a blank line separating, both leading and ending, the ticks and the included code block.
i.e. blank line, three ticks, blank line, code block, blank line, three ticks, blank line
Would you mind posting the link to the tutorial used, as I'm not able to locate what is being spoken about. Thanks
EDIT
I believe this might be the link:
If so,' as to why the example given in the puck tutorial has an "a" followed by "A"'
IMO the example demonstrates how to structure the callback within the
tap()
function, and is using a modifier to demonstrate how to capitalize a letter. Most likely the same one is used to show capitalization with the least confusion.The behind the scenes Javascript - See file end for
tap()
functionThe reference
While I defer a definitive answer to others, it might be that'I seem to reach a maximum series of characters (7)'
beneath heading "Low Level control" (first link following EDIT above)
'to send data. For Keyboards it must be an array of the form:'
[modifier, reserved, key1, key2, key3, key4, key5, key6 ]
and the
tap()
function only specify an array of 8 values. This might explain why the seventh gives the surprise that is witnessed. -
• #4
Thank you for your replay Robin.
That is the correct tutorial I was referring to.I had thought that I may have reached the array limit on the tap(), but I'm unsure as I cannot find much detail about the function in documentation. I tried only sending the character's in small chunks, exectuing the function several times but this also failed:
function sendNewTest() { NRF.sendHIDReport([2,0,4], function() { NRF.sendHIDReport([0,0,0]); }); NRF.sendHIDReport([2,0,5], function() { NRF.sendHIDReport([0,0,0]); }); NRF.sendHIDReport([2,0,6], function() { NRF.sendHIDReport([0,0,0]); }); NRF.sendHIDReport([2,0,7], function() { NRF.sendHIDReport([0,0,0]); }); NRF.sendHIDReport([2,0,8], function() { NRF.sendHIDReport([0,0,0]); }); NRF.sendHIDReport([2,0,9], function() { NRF.sendHIDReport([0,0,0]); }); NRF.sendHIDReport([2,0,10], function() { NRF.sendHIDReport([0,0,0]); }); NRF.sendHIDReport([2,0,11], function() { NRF.sendHIDReport([0,0,0]); }); }
Also I found reference using the puck.js for keyboard inputs over BLE on another topic, where Gordon suggests it would be possible at the tend of his first paragraph:
http://forum.espruino.com/conversations/307801/The other confusing thing is (which could be due to my lack of JS knowledge), when sending fewer characters (say 3 for example), why does this work:
> function sendCap() { > // Send 'a' > kb.tap(kb.KEY.A, 0, function() { > // Followed by 'b' > kb.tap(kb.KEY.B, 0, function() { > // Followed by 'c' > kb.tap(kb.KEY.C, 0); > }); > }); > > }
but not this?:
> function myCommand() { > kb.tap(kb.KEY.A, 0); > kb.tap(kb.KEY.B, 0); > kb.tap(kb.KEY.C, 0); > }
Why is it that the tap() has to be nested like that?
-
• #5
It's so that B is not sent until A is done, it waits until the event is complete before adding a new event.
-
• #6
OK, thanks Wilberforce. That clears up that part for me, I had tried using setTimeout (see below) as I though it may be a timing issue, it was not successful, but now I understand a little more.
function myCommand2() { setTimeout(sendLowA, 200); setTimeout(sendLowB, 200); setTimeout(sendLowC, 200); }
Now I just need to figure out how to send more characters across.
-
• #7
Formatting of code inside Html form TextArea element can be a bit tricky for the browser parser. There have been a few hiccups I've seen.
@Robin can you point to one? Every issue I've seen thus far (apart from hashes inside code) has been an issue with the formatting of the text the post. If you haven't used it before it can take a while to get the hang of.
I just checked this post, and there are three backticks before your code, but not after it as well (I guess you might have pasted over them by accident?). I just edited it to add them and it's fine now.
Anyway - back to the question :)
What you're doing in
sendCap
with the nesting looks fine to me. I'll check it out here and get back to you - it's possible you need a delay as the host computer may not be expecting to be getting keypresses sent so quickly.But the other main issue you have (why
sendNewTest
doesn't work) is:Why is it that the tap() has to be nested like that?
This is an issue if you're new to JavaScript. Basically it can take a while to send a keypress (if you're in a low power mode it might be half a second). Rather than stop you running any other code during that time, Espruino executes the function right away and goes on to execute what's right after. When the keypress is finally sent, it calls the callback function - and it's in that function that you need to send the next keypress - hence the nesting.
There are ways to get around that - for instance calling the same function over and over, taking characters off a string of characters to send - or you can use 'Promises' (a standard JS thing - google it for some good tutorials) to avoid the nesting as well.
-
• #8
Just got your reply...
function myCommand2() { setTimeout(sendLowA, 200); setTimeout(sendLowB, 200); setTimeout(sendLowC, 200); }
So as above this has the effect of getting all 3 functions called at the same time(ish), 200ms from when
myCommand2
was executed. You need either to do the nesting thing, or to set the timeouts to 200,400 and 600. -
• #9
Working with timeouts in an asynchronous, event driven context is the recipe for failure.
No matter what, it is only the question of 'when' will it fail: there will always be - randomly - a case where the timeout is not long enough. Furthermore, this pattern robs you of the option to take care of other things you need to do... How would you get something done expedited and being able to set a real timeout that concludes with almost certainty that something went wrong and a redo / start over is required?
You can go nested, which is also called 'callback hell', our can go with a (command or just data / character) FIFO - first-in-first-out - queue that is easy to write to and with a (dispatcher) worker that reads from the queue and does the command / send.
I do not know your overall application, but I expect that after sending quite some information over HID to your other device, you wait for some events with event data to happen before you move on in Espruino, and you moving on depends on that event data.
In multiple places of my forum published code you may find these patterns. A simple one, which just writes is a number id-ed message logger to track what the code does that, when no console is detected, writes blinking sequences in morse code for the message id. It even changes color - choses green or red led to blink for information/success messages vs. error messages. You find the code in lines 237..259 and a two typical usages in lines 29..295. After walking through the mentioned lines of code you will quite well understand what is going on an what I'm talking about when referring to a queue... especially since there are like two queues: one holds the the error or success indicator and the message code, and the other one then short and long di-and-dah-s for the morse code... The first elements select the LED to blink, almost like a command, and the second one sets up the second queue with the string with Ss and Ls with the short and long blinks.
Let's get you started on the simple part: getting a string out over BLE HID.
// setup of you convenience hidKeyBoardObject... (here or later) var hidKBD = ( // the singleton object that does all things // what you want to do in encapsulated manner { string: "" // the FIFO queue or buffer you feed with what // you want to write as HID over BLE keyboard // and feeds the tapping char by char from begin , type: function(s) { // feeding the FIFO w/ string to write this.string += s; // add string to FIFO w/ simple concat if (this.string.length = s.length) { // length are the same // which means that all was tapped and // a new tapping streak has to be started this._send(this.string.charAt(0)); // get first char out } } , _send: function(c) { // tapping the keyboard w/ a char kb.tap(kb.KEY[c],0,this._sent.bind(this)); // invoke tap and // tell tap to invoke ._sent() when done. // .bind(..) is some JS idiom you google. } , _sent: function() { // is invoked by tapping as callback when // tapping has completed... this.string = this.string.substring(1); // remove tapped char if (this.string.length > 0) { // not done yet with all chars this._send(this.string.charAt(0)); // tap next char... ;) } } } ); // setup and other code functions of yours (here or before hidKBD) function onInit() { // invoke your set up code as needed (init and getting connections) hidKBD.type("ABC"); hidKBD.type("XYZ"); }
Lines 1..25 do what inline commented/documented.
Lines 31 and 32 show a usage (EDITED to this as pointed out by post #22). Even though are executed more or less right one ofter the other, all is perfect. The second one may have been executed while the send of the first character of the first one has not even completed yet... ;-)
Note that I used kb.KEY[] freely without looking at the constraints... (which there are in order to send on character keys... So you have to come up with some notation / patterns like shift-in/out of command keys -like: "ABC~RETURN~XYZ~RETURN~" (1st ~ is shift-in and 2nd is -out) and respective code to handle such sorts of 'strings' to type / tap... Looks like a fun project to me.)
Next step would be to wrap your action code and hidKBD into a state machine that works of a command queue to do what is needed:
- some input / sensors / timeouts / intervals feed the command queue with:
- commands that invoke hidKBD.type(..)
- commands the invoke some action functions other hidKBD.type(..)
- commands that read sensors / wait for any kind of input
- commands that invoke hidKBD.type(..)
- and each execution of these commands can add command to your command queue.
As the name hints, the command queue holds on to a string of commands - array of commands / command objects - rather than just a string of characters . But the implementation pattern is the very same:
- feed FIFO queue:
.type(..)
,.exec(..)
- do 1st item in queue:
._send(..), ._exec(..)
- when item done, do:
._sent(..)
,._excd(..)
When you get there, we can feast on it with details how a command object can look like, such as real object or array / array of scalars and arrays etc, to get things code-wise done as expedient
as possible.Related topics to your 'coding challenges' are:
- Promise (Espruino JS implemented Promise; done after various community members did their own stop-gap solutions)
- Async handling (see Async handling)
- Sequencer (see sequencing - configure, require(), initializing w/ onInit() and save() ...and bind())
- Tasker (see Tasker - Task synchronizer - kind of a 'purposed' Promise)
NB: having (function) names with with leading underscore (_) indicate that these are things private to the object and should NOT be referenced or used from the outside.
- some input / sensors / timeouts / intervals feed the command queue with:
-
• #10
...sorry, could not let 'it' go before 'really' useable. I introduce(d) the single tilde (~) as shift-in/out char for special, non char-keys... and I have now to escape it as well: double-tilde (~~) means 'send one single tilde' (~). This gives you everything you need to hit all keys defined in Espruino
ble_hid_keyboard
- http://www.espruino.com/modules/ble_hid_keyboard.js - module defined inKEY
as.KEY[<key>]
. The modifiers are now on you... ;-) . Here we go with modified._send(c)
:, _send: function(c) { if (c == "~") { var sOP = this.string.indexOf("~",1); // shiftOutPos if (sOP < 0) { throw "Unmatched tilde (~) around ..." +this.string.substring(0,5); } if (sOP > 1) { c = this.string.substring(1,sOP); } this.string = this.string.substring(sOP); } kb.tap(kb.KEY[c],0,this._sent.bind(this)); }
-
• #11
Ok, so I checked into this and it actually is an issue with the amount of nesting. Not the stack as such but the execution scopes:
Uncaught Error: Maximum number of scopes exceeded
There's an issue open for this at the moment but I honestly didn't think people were really hitting it: https://github.com/espruino/Espruino/issues/948
If this has been causing a lot of problems then I could look into fixing it sooner rather than later though.
While @allObjects has posted a good solution above, here's the complete code that I just tested and which seems to work ok.
var kb = require("ble_hid_keyboard"); NRF.setServices(undefined, { hid : kb.report }); function tapMultiple(keys, callback) { // no keys? we're done! if (keys.length==0) return callback(); // tap a key kb.tap(keys[0], 0, function() { // recurse with the remainder of the array tapMultiple(keys.slice(1), callback); }); } function sendCap() { tapMultiple([ kb.KEY.A,kb.KEY.B,kb.KEY.C, kb.KEY.D,kb.KEY.E,kb.KEY.F, kb.KEY.G,kb.KEY.H,kb.KEY.I], function() { // we're done! }); } // trigger btnPressed whenever the button is pressed setWatch(sendCap, BTN, {edge:"rising",repeat:true,debounce:50});
-
• #12
Wow allObjects, thank you so much. I'm working my way through your thorough post just to make sure I understand it all fully. I'm really grateful for the time you put in. I'll report back when I've got it straight in my head.
Thanks also to Gordon, it was an issue of timing that was causing my problem.
-
• #13
@Gordon, what happens when sendCap() / tapMultiple() is re-invoked too early - before first one is done - and therefore two are running?
@tronic98776, @Gordon's running code is a good seed for implementing any command controller / command execution state machine using an array for the commands... I like it very much, especially the short form of being just two standalone functions.
-
• #14
@tronic98776, urvw... it is a pleasure... and if you see issues, point them out. So far my code is 'just' a brain dump, since I do not have a puck.js at hand for verification at this moment.
-
• #15
@allObjects yes, as you note my example doesn't cope with what happens if you send a command when one is already in progress (it will likely throw and exception or get the keys muddled) - it's just a minimal test.
-
• #17
Tue 2019.03.26
cont from #7 @Gordon
'@Robin can you point to one? Every issue I've seen thus far '
Attempting to reference a post before a link
The # symbol is ommited and text size changes
Another is attempting to *bold text inside an italicized sentence* or visa versa
Note the extra stars and lack of desired formatting
The Markdown parser?@allObjects had a lengthy code block within the last six weeks, where efforts were abandoned and mixed format was used in frustration, just unable to locate right now.
-
• #18
@Robin, code block posting works well when the block is surrounded first by proper triple back ticks as only thing on the line and at the line begin and then empty lines (before starting back ticks and ending back ticks). Tell where this happened, and I will go and fix it. The posts you refer to most likely - with 100% certainty - are not surrounded as described... - either by mistake or just pure laziness... (the header of the post edit widget has a menu button to click which press just every thing fine for the code to be pasted into it.
Just checked all my posts since begin of this year - 2019/1/1 - (where all this work with RGB LEDs and Horse Derby Race Display Board stuff happened) and could not find a messed up code block... about 12 weeks... but sure, when I get across, I will fix it.
The forum markup is simple and has limited nesting, especially when related to the special characters... That is to expect, because the text misses the needed formal language elements / symbols to make it clear.
The simplicity is intended and just right to achieve still the complexity as needed. Yep - as you can see with this last phrase w/ multiple nested mark-ups.
For inline markup, adding a blank before the begin marking and after the end marking special char (sequence) and no space after the begin marking and before the end marking special char (sequence) and similar blank lines for the block markup got me always out of the binds and lets me exactly do what I want.
-
• #19
@Robin http://forum.espruino.com/comments/14620467/ is a result of the poster not bothering to tag something as code. They just copy/pasted it in. 99% of the time that's the issue and there's very little I can do about it. I just added the three backticks at the beginning and end and it's fine now.
Attempting to reference a post before a link
A hash at the start of a line makes something into a heading in markdown. It's not the parser's fault IMO, you're just telling it you want a heading.
attempting to bold text inside an italicized sentence
This probably is the markdown parser - but honestly I think that's pretty low priority... I think most people would rather I spent my time improving Espruino than trying to debug and fix things like that :)
-
• #20
Ok, so i've been working my through some JS tutorials to try and get my head around this all. I think I understand the solution from @Gordon. And I think it would be adequate for my task. But I understand the potential issues that could be caused if a command is sent before the previous one has completed. So I really want to learn the method submitted by @allObjects that handles this event. I understand that the idea is create a "queue" of what I want to send and then add to that queue.
Currently I'm having trouble understanding what we are doing here:
, type: function(s) { // feeding the FIFO w/ string to write this.string += s; // add string to FIFO w/ simple concat if (this.string.length == s.length) { // length are the same
Line one is defining a function called hidKBD.type with the parameter s
Line two is hidKBD.string = hidKBD.string + s
Then Line three if the length hidKBD.string is the same as s.....What I'm not sure about is the parameter "s", where are we assigning it some data?
Thanks
-
• #21
sorry for being a bit short:
s
parameter is the string (of characters) you want to send.For example, if you want to send
ABC
you sayhidKBD.type("ABC");
. (...EDITED)Assuming it is the very first time just after uploading the code and nothing has been sent yet, no sending is going on, and therefore ```hidKBD.string``` is empty. ```ABC``` shows up for sending, and because ```hidKBD.string``` is empty, we have to get the sending of character by character has to be started. We have two options to do it: - 1. set a boolean state for empty, then add the string, and then depending of the stored state of empty start the process:
, type: function(s) { // feeding the FIFO w/ s(tring) to write
var empty = (this.string.length === 0); // if string is empty (no sending is going on (anymore)) this.string += s; // add string to FIFO w/ simple concat if (empty) { // 'was' empty, so sending has to be started, otherwise not
```
- 2. add the string, then check if the total string length is the same as the added string, which means - retrospectively - that string was empty before we added the string.... this is all there is to this.
Imagine you have to feed a plant with a constant low flow of water. You could baby sit this 24 x 7, 365... or, you place a bucket with a small whole in it that just meets your low flow requirement. Then - once in a while - you fill that bucket with another one by filling this other bucket from a pond by submersion and pour all into the first bucket with one big pour. If no water was left in the bucket, water starts to flow. If there was still water in it, flow continued with just a refilled bucket.
- 2. add the string, then check if the total string length is the same as the added string, which means - retrospectively - that string was empty before we added the string.... this is all there is to this.
-
• #22
The 2019.03.28
'where are we assigning it some data?'
@allObjects, I believe @tronic98776 is attempting to understand where the function call is for
type()
to pass the argument value for parameter 's' when the example reference calls functionsend()
Line 14 from #9 above references intended local function
_send()
with the leading underscore. Line 31 isn't using functiontype()
From post #1
'I'm quite new to coding JS'
Was there a newer edit of post #9 that hadn't made it to press?
-
• #23
@Robin, thanks for pointing this out... changed line 31 and 32 in posts #9 and #21 and added respective 'EDITED..' comment). --- No, there was no newer edit/version of #9 post. Usually I make a comment on content changes except for occasional fix of typos / syntax issues. Some times when composing a post I use (intermediate) saves before the post completed because I do not want to loose content on mishap, such leaving the page or loosing the session or power on machine...
-
• #24
So I think I'm making some progress here. I've been playing around with the @allObjects solution in an editor so I can get some output in the browser.
// setup of you convenience hidKeyBoardObject... (here or later) var hidKBD = ( { //member of hidKBD, currently empty string: "" , type: function(s) { this.string += s; if (this.string.length = s.length) { this._send(this.string.charAt(0)); } }, //sends first character then references _sent .this so this.string in _sent will be the string from _send _send: function(c) { console.log(c,this._sent.bind(this)); }, //subtracts first letter from string then passes new first letter to _send _sent: function() { this.string = this.string.substring(1); if (this.string.length > 0) { this._send(this.string.charAt(0)); } } } ); // setup and other code functions of yours (here or before hidKBD) //function onInit() { // invoke your set up code as needed (init and getting connections) hidKBD.type("ABC"); //hidKBD.type("XYZ"); //}
I've added some of my own commenting just to verify that I understand what is happening.
But due to the placement of the console.log on line 15, the log returns "A", but then the bind doesn't seem to envoke the _sent function and just prints the contents of _sent in the console:
//console output A ƒ () { this.string = this.string.substring(1); if (this.string.length > 0) { this._send(this.string.charAt(0)); } }
How can I set up something so I can play with the code and have it returning a something representative of the BLE HID output to a browser.
Many thanks guys.
-
• #25
Sun 2019.03.31
'the log returns "A", but then the bind doesn't seem to envoke the _sent function'
From #24 Line 31
hidKBD.type("ABC");
of your version is sending a string or array of chars.From #24 Line 10
this._send(this.string.charAt(0))
is internally sending an individual character, element zero of the string.From #9 Line 14 @allObjects
kb.tap(kb.KEY[c],0,this._sent.bind(this));
code line is using an array. Was the new attempt a mismatch done intentionally?Have you discovered W3Schools which always helped me with their 'TryIt' editor
https://www.w3schools.com/jsref/jsref_charat.asp
https://www.w3schools.com/js/js_function_call.asp
https://www.w3schools.com/js/js_function_parameters.asp' have it returning a something representative of the BLE HID output to a browser'
@tronic98776 do these control Puck-Web Page tutorials provide some insight?
Beneath heading 'A Website'
https://www.espruino.com/Quick+Start+BLE#puckjs
https://www.espruino.com/Web%20Bluetooth
https://www.puck-js.com/go
Hello
And thanks in advance for any help. I'm quite new to coding JS, but always keen to learn.
I'm trying to programme my Puck.js to connect to another device as BLE HID Keyboard. I have successfully got this working and I am able to send a character or two to the device after pressing the button on the puck. What I now want to do is send a whole word or sentence from the button press. Here is where I am having issues. I have tried creating many different functions to do this, but none of it seems to work successfully. I seem to reach a maximum series of characters (7), before strange things happen like the the seventh character repeating endlessly.
I tried mapping each of the kb.tap commands to an individual function, but this didn't help things and then I tried using the low level commands too, but this also resulted in the same issues. I'm also a bit confused as to why the example given in the puck tutorial has an "a" followed by "A", but rather than them being separate lines within the function, they are nested within one another (this is likely due to my lack of JS experience), but I couldn't find reference to this practice anywhere.
On my code below you can see I tried to construct a sequence of A,B,C,D,E,F,G,H... to send to my connected device. There are several functions in there that I tried; sendCap, sendLowLevel, sendNewTest and myCommand, none of which worked.