WebSocket Interactive UI

Posted on
  • NOTE: This done with an ESP-12E running Espruino on the latest Travis builds of User1 and User2 as they include crypto support which is needed for the WebSocket Server.

    This is an example of using WebSockets for real-time interaction, it is adapted from the Interactive Web UI example. The Espruino acts as both the WebSocket Server and a web server to serve the page to the client.

    The code for the webpage for this client is in this Gist on GitHub as it was too large to fit on the forum. This code send the value over a WebSocket every time the value is updated instead of sending a POST request when the mouse is lifted like the Interactive Web UI demo.

    The Espruino code is just below (New Code in this post). Just upload it to your ESP after setting Wifi up and saving that. I'd recommend visiting the page from a smartphone, the touch controls feel fluid. The webpage is a (espIP):8000

    var page = '<body style="width:100%;height:100%;overflow:hidden;"><svg style="width:100%;height:100%;"  viewbox="0 0 500 500" id="svg"><path style="fill:#80e5ff;fill-opacity:1;fill-rule:nonzero;stroke:none" d="M 250 0 C 111.92882 3.7895613e-14 0 111.92882 0 250 C -1.249508e-14 341.05067 48.689713 420.72528 121.4375 464.4375 L 154.625 409.40625 C 100.50052 376.95218 64.28125 317.69934 64.28125 250 C 64.28125 147.43284 147.43284 64.28125 250 64.28125 C 352.56716 64.28125 435.71875 147.43284 435.71875 250 C 435.71875 317.53896 399.66155 376.65256 345.75 409.15625 L 378.71875 464.34375 C 451.37991 420.61135 500 340.98541 500 250 C 500 111.92882 388.07118 -1.8947806e-14 250 0 z " id="ring"/> <rect style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none" id="needle" width="16" height="80" x="242"/> <text xml:space="preserve" style="font-size:122.59261322px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Helvetica;-inkscape-font-specification:Helvetica" x="250.01915" y="845.31812" id="text"><tspan id="label" x="250.01915" y="292.95594">0</tspan></text> <path style="fill:#d5f6ff;fill-opacity:1;fill-rule:nonzero;stroke:none" id="up" d="m 294.75099,133.39225 -90.93056,0 45.46528,-78.748173 z" transform="matrix(0.61903879,0,0,0.61903879,95.682477,91.16682)" /> <path transform="matrix(0.61903879,0,0,-0.61903879,95.682477,408.80767)" d="m 294.75099,133.39225 -90.93056,0 45.46528,-78.748173 z" id="dn" style="fill:#d5f6ff;fill-opacity:1;fill-rule:nonzero;stroke:none" /></svg><script>var ws=new WebSocket("ws://"+location.host,"protocolOne"),timestamp=Date.now(),pos=50,dragging=!1;function $(a){return document.getElementById(a)}function setPos(a){0>a&&(a=0);100<a&&(a=100);pos=a;100<Date.now()-timestamp&&(timestamp=Date.now(),ws.send(pos));$("label").textContent=pos;a=2.8*(pos-50);$("needle").setAttribute("transform","rotate("+a+" 250 250)")}setPos(pos);function dragStart(){dragging=!0;$("ring").style.fill="#ff0000"}document.addEventListener("mousemove",function(a){if(dragging){a.preventDefault();var b=$("svg");setPos(Math.round(180*Math.atan2(a.clientX-b.clientWidth/2,b.clientHeight/2-a.clientY)/Math.PI/2.8+50))}});document.addEventListener("mouseup",function(a){dragging=!1;$("ring").style.fill="#80e5ff";$("up").style.fill="#d5f6ff";$("dn").style.fill="#d5f6ff"});function touchHandler(a){var b=a.changedTouches[0],c="";switch(a.type){case "touchstart":c="mousedown";break;case "touchmove":c="mousemove";break;case "touchend":c="mouseup";break;default:return}var d=document.createEvent("MouseEvent");d.initMouseEvent(c,!0,!0,window,1,b.screenX,b.screenY,b.clientX,b.clientY,!1,!1,!1,!1,0,null);b.target.dispatchEvent(d);a.preventDefault()}document.addEventListener("touchstart",touchHandler,!0);document.addEventListener("touchmove",touchHandler,!0);document.addEventListener("touchend",touchHandler,!0);document.addEventListener("touchcancel",touchHandler,!0);$("ring").onmousedown=dragStart;$("needle").onmousedown=dragStart;$("up").onmousedown=function(a){a.preventDefault();this.style.fill="#00ff00"};$("dn").onmousedown=function(a){a.preventDefault();this.style.fill="#ff0000"};$("up").onmouseup=function(a){setPos(pos+10)};$("dn").onmouseup=function(a){setPos(pos-10)};</script></body>';
    
    function onPageRequest(req, res) {
      res.writeHead(200, {'Content-Type': 'text/html'});
      res.end(page);
    }
    
    var server = require('ws').createServer(onPageRequest);
    server.listen(8000);
    server.on("websocket", function(ws) {
        ws.on('message',function(msg) { print("[WS] "+JSON.strin
    

    Immediately after uploading the code the memory looks like this

    >process.memory();
    ={ "free": 785, "usage": 615, "total": 1400, "history": 532 }
    

    Then when the first user connects

    ERROR: Out of Memory!
    Execution Interrupted
    >process.memory();
    ={ "free": 588, "usage": 812, "total": 1400, "history": 1 }
    

    I believe the history is cleared and the out of memory error isn't a problem

  • Did you check heap ?

    require("ESP8266").getState()
    

    Another question, could it be an option to store page in flash and use E.memoryArea ?(http://www.espruino.com/Reference#t_l_E_memoryArea)

  • If you get Execution Interrupted then something would have stopped working halfway though. It silently deletes history before complaining about low memory.

    Low free heap wouldn't have caused that error.

    My guess is that res.end(page); is what's causing the problem - as the page is pretty big, and is just being appended to the output buffer, you're basically doubling your memory usage when a page is requested.

    As such, even if you put the page into flash memory this might help for now, but you'll still hit this problem if you make the page much bigger.

    There's some example code on the internet page showing how you can use the drain event to supply data one bit at a time, and you could do that - serving out a few hundred bytes at a time.

    But for you, the simplest solution would actually be to have 2 files - one script and one HTML. You then include the JS from the HTML file, and serve up the 2 different files in onPageRequest.

  • Take a look here:

    http://forum.espruino.com/conversations/283045/#comment12837928

    This stores page content in flash, and uses the .pipe to stream the content out on demand.

    You can store the content on your gist, fetch into flash. Then your code above can pull the content from flash.

    Edit: as gist requires https:// access you can use the wget to get this content. I have used a local node server to cache code.

  • Oh... And the latest builds allow you to use the 1mb above the firmware...

  • @Gordon Thanks, using a separate request for the script solved that. Now memory usage looks like this before and after the clients connection.

    >process.memory();
    ={ "free": 769, "usage": 631, "total": 1400, "history": 551 }
    [WS] "40"
    >process.memory();
    ={ "free": 559, "usage": 841, "total": 1400, "history": 199 }
    

    New Code

    var page = '<body style="width:100%;height:100%;overflow:hidden;"><svg style="width:100%;height:100%;"  viewbox="0 0 500 500" id="svg"><path style="fill:#80e5ff;fill-opacity:1;fill-rule:nonzero;stroke:none" d="M 250 0 C 111.92882 3.7895613e-14 0 111.92882 0 250 C -1.249508e-14 341.05067 48.689713 420.72528 121.4375 464.4375 L 154.625 409.40625 C 100.50052 376.95218 64.28125 317.69934 64.28125 250 C 64.28125 147.43284 147.43284 64.28125 250 64.28125 C 352.56716 64.28125 435.71875 147.43284 435.71875 250 C 435.71875 317.53896 399.66155 376.65256 345.75 409.15625 L 378.71875 464.34375 C 451.37991 420.61135 500 340.98541 500 250 C 500 111.92882 388.07118 -1.8947806e-14 250 0 z " id="ring"/> <rect style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none" id="needle" width="16" height="80" x="242"/> <text xml:space="preserve" style="font-size:122.59261322px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Helvetica;-inkscape-font-specification:Helvetica" x="250.01915" y="845.31812" id="text"><tspan id="label" x="250.01915" y="292.95594">0</tspan></text> <path style="fill:#d5f6ff;fill-opacity:1;fill-rule:nonzero;stroke:none" id="up" d="m 294.75099,133.39225 -90.93056,0 45.46528,-78.748173 z" transform="matrix(0.61903879,0,0,0.61903879,95.682477,91.16682)" /> <path transform="matrix(0.61903879,0,0,-0.61903879,95.682477,408.80767)" d="m 294.75099,133.39225 -90.93056,0 45.46528,-78.748173 z" id="dn" style="fill:#d5f6ff;fill-opacity:1;fill-rule:nonzero;stroke:none" /></svg><script src="/interact.js"></script></body>';
    var script = 'var ws=new WebSocket("ws://"+location.host,"protocolOne"),timestamp=Date.now(),pos=50,dragging=!1;function $(a){return document.getElementById(a)}function setPos(a){0>a&&(a=0);100<a&&(a=100);pos=a;100<Date.now()-timestamp&&(timestamp=Date.now(),ws.send(pos));$("label").textContent=pos;a=2.8*(pos-50);$("needle").setAttribute("transform","rotate("+a+" 250 250)")}setPos(pos);function dragStart(){dragging=!0;$("ring").style.fill="#ff0000"}document.addEventListener("mousemove",function(a){if(dragging){a.preventDefault();var b=$("svg");setPos(Math.round(180*Math.atan2(a.clientX-b.clientWidth/2,b.clientHeight/2-a.clientY)/Math.PI/2.8+50))}});document.addEventListener("mouseup",function(a){dragging=!1;$("ring").style.fill="#80e5ff";$("up").style.fill="#d5f6ff";$("dn").style.fill="#d5f6ff"});function touchHandler(a){var b=a.changedTouches[0],c="";switch(a.type){case "touchstart":c="mousedown";break;case "touchmove":c="mousemove";break;case "touchend":c="mouseup";break;default:return}var d=document.createEvent("MouseEvent");d.initMouseEvent(c,!0,!0,window,1,b.screenX,b.screenY,b.clientX,b.clientY,!1,!1,!1,!1,0,null);b.target.dispatchEvent(d);a.preventDefault()}document.addEventListener("touchstart",touchHandler,!0);document.addEventListener("touchmove",touchHandler,!0);document.addEventListener("touchend",touchHandler,!0);document.addEventListener("touchcancel",touchHandler,!0);$("ring").onmousedown=dragStart;$("needle").onmousedown=dragStart;$("up").onmousedown=function(a){a.preventDefault();this.style.fill="#00ff00"};$("dn").onmousedown=function(a){a.preventDefault();this.style.fill="#ff0000"};$("up").onmouseup=function(a){setPos(pos+10)};$("dn").onmouseup=function(a){setPos(pos-10)};';
    
    function onPageRequest(req, res) {
      var a = url.parse(req.url, true);
      if(a.pathname == "/interact.js"){
        res.writeHead(200, {'Content-Type': 'application/javascript'});
        res.end(script);
      }else{
        res.writeHead(200, {'Content-Type': 'text/html'});
        res.end(page);
      }
    }
    
    var server = require('ws').createServer(onPageRequest);
    server.listen(8000);
    server.on("websocket", function(ws) {
        ws.on('message',function(msg) { print("[WS] "+JSON.stringify(msg)); });
        ws.send("Hello from Espruino!");
    });
    
  • Hi Ducky, thanks for this example.
    I added a line to print the value of msg in the console...

    var server = require('ws').createServer(onPageRequest);
    server.listen(8000);
    server.on("websocket", function(ws) {
        ws.on('message',function(msg) {
          console.log("Pos = "+msg);
        });
    });
    

    I've connected to the ESP8266 over TCP using the Espruino Web IDE so I can see the console.

    On the PC I see the value updating as I move the controls but when I try the same from a tablet or phone I don't see the value of msg updating, I've tried chrome and safari browsers, why?

  • Hey, You shouldn't need to add anything to the code to make it print the message to the console, the

    ws.on('message',function(msg) { print("[WS] "+JSON.stringify(msg)); });
    

    line does that. However, perhaps you need to JSON.stringify it first.
    I just pasted the code I posted above your comment and put it on my ESP, connected and it worked perfectly from both my desktop Chrome, and my Android Chrome. Perhaps you have an out of date web-browser that doesn't support web-sockets?

  • I tried again with stringify and checked my browsers are up to date and working with websockets at websocketstest.com and still no joy on the mobile devices but it works beautifully on the PC. Maybe it's IOS?

  • Eventually, I found the answer in this post , thanks Gordon and Ollie!
    The browsers on iphone are all based on something that's not friends with HTTP/1.o.

  • sorry, I didn't see this or I'd have jumped in. The Hex editor replacement is very straightforward, are you up and running on iOS?

  • yep, works a treat. It's all good

  • Just noticed the HTTP/1.0 thing is now fixed in v91. Thanks!

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

WebSocket Interactive UI

Posted by Avatar for MrTimcakes @MrTimcakes

Actions