-
• #2
Thr 2021.04.08
Hi @jgrizou and Kudos on your ease of project success!
'So first, thanks for developing all this!'
I know that @Gordon will appreciate your kind words for all the hard work and determination he has put into the the creation and success of the Espruino project, making it's ease of use a core goal for evey newcomer.
'The ability to publish information from the embedded device to the webpage'
There is a great starting page tutorial with embedded 'Try Me' buttons for each snippet that demonstrate BLE communication both ways:
note: mentally substitute 'MDBT42Q' for 'Puck.js' and use those snippets as is
http://www.espruino.com/Web+Bluetooth
I'm only able to provide some assistance using Windows10, but this page should assist visually in the connectivity part:
I'll defer to Gordon for the technical explanation for the eval() funtionality, and your comments/discussions questions. -
• #3
Hi! That looks like a really neat little robot design! Are you planning to publish it anywhere?
The ability to publish information from the embedded device to the webpage
That is actually possible right now. It's not well documented for UART.js, but there is an example for the
Puck.js
library (which is just like UART.js, except it's Bluetooth only). You basically callconnect
and then you get a callback whenever data is received (which you can do by callingBluetooth.println
).https://www.espruino.com/Web+Bluetooth#two-way-communications
or
https://www.espruino.com/Bangle.js+Data+Streaming
There was some talk of whether this could be made easier though - effectively being able to register a
UART.on('myevent', callback)
function so you could send multiple event types without having to filter them yourself.The ability to use an embedded device as if it was a javascript module
That's really neat! I guess we could have a UART object defined on the device itself, and then just query the keys for that.
Another option is actually you can dynamically handle key accesses using
Proxy
: https://spin.atomicobject.com/2018/07/02/javascript-proxy-object/While that won't work on Espruino, on the PC you could happily call
UART.myfunction(...)
and it'd translate it into a similar call:Espruino = {}; Espruino = new Proxy(Espruino, { get: (target, prop) => function() { console.log(prop+"("+[].map.call(arguments,x=>JSON.stringify(x)).join(",")+")") }, has: (target, prop) => true }); Espruino.test(1,2,3,"Hello",{a:42}); // prints 'test(1,2,3,"Hello",{"a":42})'
So if you call a function that doesn't exist it's no big deal - it just creates a referenceError on Espruino.
In a way, I guess while this could be in UART.js, maybe it even makes sense to built it as a separate library on top of it, that is Espruino specific?
-
• #4
Thanks @Robin @Gordon for sharing all this, it makes total sense and sounds feasible!
https://www.espruino.com/Web+Bluetooth#two-way-communications
I tried with good success.
UART.on('myevent', callback)
is a great way to implement this combined with a function on the embedded side and format events.I guess we could have a UART object defined on the device itself, and then just query the keys for that.
Another option is actually you can dynamically handle key accesses using Proxy: https://spin.atomicobject.com/2018/07/02/javascript-proxy-object/Amazing! The Proxy thing is great and even more hands-off for the user.
maybe it even makes sense to built it as a separate library on top of it, that is Espruino specific?
Understanding this better now, I agree that a separate library makes more sense. I will work on a first version and share it here when it is ready ;)
That looks like a really neat little robot design! Are you planning to publish it anywhere?
Thanks! It is a quick prototype but you are right, I will make a repo with some basic info (BOM, wiring, code, etc) and share it with the community.
-
• #5
That's great - thanks!
-
• #6
Hi, I have tried to work on this a bit and got confused by the use of connections in uart.js.
I would like to use the nice utility functions UART.write() and UART.eval() but do not want them to trigger their own connect event (that is showing up the select a port screen). I would like to connect only once when the user decides to and be able to all operations from there.
But using the connection.on("data", cb) pattern and the write() and eval() helper function from UART seems incompatible as the UART.write() and UART.eval() are using their own separate internal connection variable.
It feels not optimal to have to duplicate that part of the code: https://github.com/espruino/EspruinoWebTools/blob/13032d0e862d9976ed5eed33b42f07e93e6c25c3/uart.js#L403-L500 entirely to replicate the UART functionalities from a connection that I would open using the method described in https://www.espruino.com/Web+Bluetooth#two-way-communications
This is actually commented in the code by saying that the UART.connect function output a connection different from the one used by write() and eval(), see image attached.
I guess my questions are:
- Is there a way to do both these things at once? For example adding a custom connection.on("data", cb) on the same connection used by UART.write() and UART.eval().
- If not, because this would interfere with each others, is there a way to connect to two connections at the same time? So I could develop a tool that can use both functionalities but the user would only see one connection screen.
If that does not work, I guess I will have to reimplement something similar to write and eval functionality but with the connection.on("data", cb) working somehow on the with it.
I am guessing I am a bit confused with how connection works under the hood but I hope that makes some sense :)
Thanks in advance for any guidance on that!
1 Attachment
- Is there a way to do both these things at once? For example adding a custom connection.on("data", cb) on the same connection used by UART.write() and UART.eval().
-
• #7
As I understand it, UART.write/eval will create a connection (internally) and will then use that - so if you're writing to just a single device then that's all you need. You're prompted on the first call and after that it 'just works'.
It feels not optimal to have to duplicate that part of the code:
Yes, you're right. The write/eval there should really be some common functionality in the connection object, or at the very least should take a connection as an argument so could be used on multiple connections. Are you interested in making that change, or should I look at it?
Is there a way to do both these things at once? For example adding a custom connection.on("data", cb) on the same connection used by UART.write() and UART.eval().
I believe after you're connected, you can just do
UART.getConnection().on('data', ...)
? -
• #8
Thanks for the feedback!
As I understand it, UART.write/eval will create a connection (internally) and will then use that - so if you're writing to just a single device then that's all you need. You're prompted on the first call and after that it 'just works'.
Yes. My only issue is that I want to explicitly connect to the device before sending commands, to be sure the robot is ready and two way communication is established before starting the control loops.
I guess a work around is to trigger a connect by sending an empty
UART.write('\n')
and then useisConnected
andgetConnection
. I will try and let you know how it goes! -
• #9
I guess a work around is to trigger a connect by sending an empty UART.write('\n')
Yes, that's what I'd do... Sometimes it's good practice to send a
"\x03"
(ctrl-c) anyway as if there was any command left on the REPL's input line that will clear it out -
• #10
Quick update. I got UART.write, UART.eval, and a custom
UART.on('myevent', callback)
all working simultaneously.When I register a custom
connection.on('data', ...)
callback, it overwrites the on('data', ...) callback defined within UART.write (see https://github.com/espruino/EspruinoWebTools/blob/13032d0e862d9976ed5eed33b42f07e93e6c25c3/uart.js#L470-L474), so I had to copy those few lines back into my custom one. A bit hacky as I inject data back into the connection from the outside.Code is very rough right now, just working my way around things. But, if curious, for now code is here: https://editor.p5js.org/jgrizou/sketches/Si31amD_p (see robot.js and sketch.js) and testable at https://editor.p5js.org/jgrizou/present/Si31amD_p
Next I will try the proxy trick :)
-
• #11
That's great - thanks! I just created an issue for UART.js to keep track of this: https://github.com/espruino/EspruinoWebTools/issues/6
I'm not sure when I'll get time, but hopefully I'll get those issues fixed.
-
• #12
I am banging my head trying to make proxy work for all cases which seems very hard, if not impossible. Ideally I would like both
get
,set
, andapply
to work, so we can do:robot = EspruinoDevice(UART) var p = await robot.speed; // assuming speed is a variable defined on the repl var temp = await robot.getTemperature() // assuming getTemperature() is defined on the repl await robot.move(someparameters) await robot.speed = 2*p; // changing the speed variable
The problem I am having is knowing if
speed
ormove
are object or function which change the expected behaviour from the proxy. I tried many approaches, but I start to feel it is impossible because we can only know if it is a function or the object after we return something and see if it is later called as a function again. I then tried to go via promises to see if I could decide this later but the same problem came back in the end. I am running short of ideas right now.Some ressources I used:
- https://stackoverflow.com/questions/44441259/determining-if-get-handler-in-proxy-object-is-handling-a-function-call
- https://gist.github.com/mrharel/592df0228cebc017ca413f2f763acc5f and https://medium.com/front-end-weekly/using-proxy-to-track-javascript-class-50a33a6ccb
- https://github.com/kozhevnikov/proxymise
I am quite stuck so I will stop now. I am aware all can be solved by relying on setter and getter function on the device, e.g getSpeed() and setSpeed(), and then building the proxy assuming all
get
calls are function calls. But I enjoy the challenge, it would be so neat to get all cases working seamlessly.Any thoughts on this?
As an alternative route, is there a method to list all user defined variable and function on the Espruino device? That way we could ask for that list when connecting and decide what can and cannot be called.
==
One learning to maybe add to the issue -> it would be good to have the eval function return 'undefined' when it receives 'undefined' (right now it catch an error, print it, and call the cb with null instead). This way we can use eval for all actions and know if it actually crashed or ran well but got undefined back. That would be around here: https://github.com/espruino/EspruinoWebTools/blob/13032d0e862d9976ed5eed33b42f07e93e6c25c3/uart.js#L491-L497
Also I implemented a simple promise system on top of eval, works very well!
- https://stackoverflow.com/questions/44441259/determining-if-get-handler-in-proxy-object-is-handling-a-function-call
-
• #13
Sat 2021.04.24
'is there a method to list all user defined variable and function on the Espruino device?'
I presume after upload to the device? Will
dump()
do the trick?'but I start to feel it is impossible because we can only know if it is a function or the object after we return something and see if it is later called as a function again'
Honestly @jgrizou, I'm not sure I understand the dilemma, is this what you are after?
See Heading 'Complex Data' (near end of page)
https://www.w3schools.com/js/js_datatypes.asp
The typeof operator can return one of two complex types:
function
object -
• #14
Thanks @Robin
what you are after?
Imagine we have this code on the device:
var speed = 0.5; function move(arg) { // do something with arg }
I am trying to create a
robot
object in the webbrowser that is connected to the device via UART, such thatrobot.speed
translate into a call toUART.eval('speed')
, androbot.move(value)
translate to call toUART.eval('move(value)')
. But in a generic way, so that the objectrobot
does not really have the propertiesspeed
andmove
and any other properties could be called without defining them explicitly, hence the use of Proxy as per Gordon suggestion.Check again the first few posts for more details. I hope that makes some sense.
==
I might have found a way around my problem with an additional call
eval('typeof prop')
to check if the property exist and is a function or not. -
• #15
I might have found a way around my problem with an additional call eval('typeof prop') to check if the property exist and is a function or not.
Ok that approach work. It is a tricky code with a few promise and using https://github.com/kozhevnikov/proxymise on top.
Code is very rough right now, just working my way around things. But, if curious, for now code is here: https://editor.p5js.org/jgrizou/sketches/Si31amD_p (see robot.js and sketch.js) and testable at https://editor.p5js.org/jgrizou/present/Si31amD_p
For example, if you have a espruino device with a code like:
var speed = 0.5; var on; function toggle() { on = !on; digitalWrite(LED1, on); } function add(x, y) { return x+y; }
You can load this page https://editor.p5js.org/jgrizou/present/Si31amD_p, click connect button and connect to the device.
Then in the webbrowser console the following commands should work:
await proxymiseRobot.speed // 0.5 await proxymiseRobot.toggle() // light should toggle await proxymiseRobot.add(2,2) // 4
A lot of cleaning, refactoring, bulletproofing is required, but the conceptual basis are there and it seems to work so I have hope, see head of sketch.js at https://editor.p5js.org/jgrizou/sketches/Si31amD_p.
That was fun anyway! I will try to handle the
set
route in the proxy too.In time, I will make it a standalone and actually usable library but for now I am exploring.
-
• #16
That looks great! While
dump
does work, it does dump everything. You could just printObject.keys(global)
?Good point about
eval(undefined)
. Does this actually fail, or is it that accessing a variable that doesn't exist is causing a reference error?I'm not sure if you found this, but you might be able to make
get
return a function, but with avalueOf
method (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/valueOf) that got the value rather than executing a function? It seems that querying the type might be preferable anyway though. -
• #17
Thanks fo the feedback!
Object.keys(global)
Yes!
for (a in global) { console.log(a, "is a", typeof eval(a)) }
. I will have a think if it makes sense to use this instead.Good point about eval(undefined). Does this actually fail, or is it that accessing a variable that doesn't exist is causing a reference error?
I meant
UART.eval('functionwithnoreturnstatement()', cb)
, will try to evaluate the result of the function call on the device, getundefined
back, then try to interpret is as a JSON (https://github.com/espruino/EspruinoWebTools/blob/13032d0e862d9976ed5eed33b42f07e93e6c25c3/uart.js#L492). It will fail, be send to the catch, and call the callback withnull
(https://github.com/espruino/EspruinoWebTools/blob/13032d0e862d9976ed5eed33b42f07e93e6c25c3/uart.js#L496). So we cannot make the difference between a successful call to a real function and a call to something that does not exist or failed to execute properly.In other words, in the example above,
await proxymiseRobot.toggle()
will returnnull
, instead ofundefined
.This try/catch mechanism is great when we got an error and should be kept, but would be nice to have a third case for
undefined
like:if (d.trim() == "undefined") { cb(undefined) } else { // do what is already being done }
but you might be able to make
get
return a function, but with avalueOf
methodDo you mean something like this?
var a = function() { return 12;} a.prototype.valueOf = function() { return 42;}; console.log(a) // would like 42, got function() { return 12;} console.log(a()) // would like 12, got 12
Would be perfect indeed, but does not seem to work for functions.
-
• #18
Thanks - that's great. Just filed an issue for it here: https://github.com/espruino/EspruinoWebTools/issues/7
The issue may actually be more widespread - because I guess if you
eval([1,2,undefined,3])
you will have similar issues as well. It might be that we need to add a 'JSONish' parser and then use theE.toJS
function in Espruino to convert the data. -
• #19
Nothing new on that front yet but I just found out about https://www.npmjs.com/package/proxy-deep. Posting here to keep track.
-
• #21
@Abhigkar thanks for asking, I just exported and uploaded them at https://github.com/jgrizou/phonebot/tree/main/stl
You can find the original design on OnShape at https://cad.onshape.com/documents/858420b45d74a6c130741a27/w/ccd74c6c91d1447417ffa0b3/e/e34cca09c53379cb87889070?renderMode=0&uiState=62501d0795faa529ca2ae411
You should be able to copy this and make modification as needed.
I hope this helps!
-
• #22
@jgrizou_email_login /@jgrizou
Thanks :). I love to build this..
Hi Gordon, all,
I started playing making a robot controlled via a webpage using Espruino, see photos attached and video here: https://youtu.be/xKEqqFrYgNQ
Building the robot took a few hours. And both embedded code + remote control via basic webluetooth page using uart.js less than one hour, I really didn't expect it would be so quick to get a MVP, the Espruino toolchain and REPL spirit is amazing. See very basic interface here: https://editor.p5js.org/jgrizou/present/osAAXLUtL. It also seem that I could connect several webpage to the same robot which is quite cool!
This was mind blowing to me on two aspects:
So first, thanks for developing all this!
--
I would like to brainstorm uart.js extension ideas here and collect the community opinion as to wether it is technically feasible and where I should start to implement it if I wanted to.
At the moment, I can see two ways to use the UART, write and eval:
And my understanding is that eval is calling write with an eval statement
"\x10eval(process.env.CONSOLE).println(JSON.stringify('+expr+'))\n"
. I not 100% sure how this work but it makes sense.Source code here: https://github.com/espruino/EspruinoWebTools/blob/master/uart.js
--
I would be interested in:
1) The ability to publish information from the embedded device to the webpage. For example, I might want to send an event if a sensor changes value, say my robot bumped into an object. Ideally, we would be able to a callback in uart.js for all spontaneous incoming messages. And the user can then sort them as they wish.
Maybe via a function sendToUart(), which can be tested using something like:
By doing
setInterval(function() {console.log({'value': true}}), 500);
, I could see the packet being sent to the webpage but they would need to be processed and formatted. It might also make it harder to differentiate between self-published messages and messages sent after an eval() was called maybe. Althought eval() calls seemed to work well despite that noise.Just to be clear, I am interested in self-published events from the robot. That is I want to avoid having to call eval() from the browser at fixed interval.
2) The ability to use an embedded device as if it was a javascript module. This is mostly to avoid having to use string calls in the write and eval function. That would require having an additional function that, on uart.js connection, could read all function available on the embedded device and publish them under an object so we could do something like this on the webpage:
In the background, all these call would use uart.js write and eval function. Function to be published could be made explicit via some sort of export statement in the embedded code like:
exportUart({stop, left, right})
Similar to module export in javascript.
--
Do you have thought on this? To be clear I am not asking anyone to implement this, but rather for some comments/discussions on:
Thank you for your time reading this long post, any insight from the community would be very appreciated!
Jonathan
2 Attachments