JavaScript With Hardware [Part Three]: Simple Web Interface

JavaScript With Hardware [Part Three]: Simple Web Interface

Part Three in a series of using JavaScript with Hardware! In this part, Iain shows how to set up a server and making a simple web interface!

  • 3,021 views
  • 0 comments
  • 20 respects

Components and supplies

Apps and online services

About this project

Hey! Welcome back! Are you ready to make some more code for our simple Nodebot? I sure hope so! Last time, we covered using modern JavaScript techniques and making sure our code is consistent. Now that we've got our code modernised, cleaned up, and using ESLint rules, we can progress on making a Node.JS Server and a simple Web Interface for our Nodebot!

If you've missed some of the earlier parts of this series and need to catch up, you can read about JavaScript and Nodebots and Making A Simple Nodebot, then learn about Using Modern JavaScript.

Anyway, let's get going!

What we'll be making

We're about to step up our Nodebot game here, since we'll be making a very simple interface using HTML, CSS and JavaScript. To serve the HTML, we'll also be using Node.JS and Express to make our Server, Webpack to bundle our client-side JavaScript, and use Socket.IO to communicate events from the client-side to the server, and vice versa.

We're just going to make a simple web page with a button, and this will be used to toggle the LED on and off. That's all we need to have for this, it's a simple build to get you into making a Node server and how to control our simple Nodebot with a web interface.

With that being said, we've got a lot of work to do, so let's get started!

Updating Our Simple Nodebot and Editorconfig

To start this off, we'll be updating our Simple Nodebot and its code since we no longer require a physical button, since we'll be using one through HTML, JS and WebSockets. So, first thing we need to do, is remove the button and its wires from our Nodebot.

Next, we'll remove the button's code in our index.js file. Again, we no longer need it, so we'll just delete all of our button code, including the toggle functionality.

// @flow
/* eslint-disable no-console */
import { Board, Led } from 'johnny-five';
// [...] Our setup code lives here...
// When the board is connected, turn on the LED connected to pin 9
board.on('ready', function() {
  console.log('[johnny-five] Board is ready.');
  // Make a new Led object and connect it to pin 9
  const led = new Led(9);
  // Switch it on!
  led.on();
  // [...] The rest of our code lives here...no more button code!
});

We've now removed any instance of code for the physical button! Next, before we dive into making more code, we should some more settings to our .editorconfig file. This is opinionated, but I suggest using tabs instead of spaces when working in HTML. The size of the indent is up to you.

# http://editorconfig.org
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.json]
insert_final_newline = ignore
[*.html]
indent_style = tab
indent_size = 4

One last thing we need to add is to our .flowconfig — some bits of our code need to ignored by Flow otherwise it will be a bit upset, so we'll update it with this line of code.

[options]
suppress_comment= \\(.\\|\n\\)*\\flow-disable-next-line

Now if we add before a line of code //flow-disable-next-line, Flow will immediately ignore the proceeding line and won't give an error!

Now we've set up our previous code for what's to come next! Let's get going and make our server and interface!

Setting up the Node Server and HTML

Let's get underway to making a Node server! Before we begin, we need to install an extra dependency called Express — a JavaScript Web Application framework.

With Express, we can build a web application that can serve our interface, and can handle requests as well as serve files easily. To do this, we add it to our dependencies with Yarn.

yarn add express

We'll need to update our code in index.js as this will be our server file as well as our code for the Arduino, neatly bundled in one file. Let's make our Server!

// @flow
/* eslint-disable no-console */
import { Board, Led } from 'johnny-five';
import express from 'express';
import { Server } from 'http';
// Make a new Express app
const app = express();
// flow-disable-next-line
const http = Server(app);
// Set the server to port 8000 and send the HTML from there.
http.listen(8000, () => {
 console.log('Server running on Port 8000');
});
// [...] Our Johnny-Five code lives here...

With our code above, we've imported Express and a Server module from the http package in Node.JS, which comes as a default. We then make a new Express application, and then make a new HTTP Server with the Express App.

For this instance, we'll hard-code the Server to run on Port 8000 and listen for any requests. Now let's make our HTML to serve on the root of the Server!

The HTML for this is quite simple, it's simply a button wrapped in a div element that acts as a container, and will be centered on the center of the page using CSS.

<!doctype html>
<html>
    <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <title>Simple Nodebot Interface</title>
        <meta name="viewport" content="width=device-width, initial-scale=1">
    </head>
    <body>
        <div class="page-wrap">
            <!-- LED Toggle button for controlling the LED on the Server-Side -->
            <button class="led-toggle" id="led-toggle">Turn Off LED</button>
        </div>
       <script src="/static/bundle.js"></script>
    </body>
</html>

Quick and simple HTML! We've assigned an ID to our button so we can use JavaScript to get the button by its ID. We've also added a CSS class to the button for styling, and wrapped in a div.page-wrap element so it's perfectly centered on the page!

Now we just need to apply some CSS to our content — we use a simple reset to our elements (note: we're making a simple project, we can be a bit dirty with our CSS!), and use CSS Flexbox to completely center the button on the page.

<style>
    /**
     * Quick and dirty reset
     */
    * {
        margin: 0;
        padding: 0;
        border: 0;
    }
    /**
     * Set box-sizing
     */
    html {
        box-sizing: border-box;
    }
    *,
    *:before,
    *:after {
        box-sizing: inherit;
    }
    /**
     * Set up page background colour and typography
     */
    body {
        background: #22cece;
        font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
        font-size: 1rem; /* 16px */
        line-height: 1.5;
    }
    /**
     *
     * Page Wrapper Element
     *
     * Set to flexbox and make sure all items are aligned to the center.
     *
     */
    .page-wrap {
        width: 100%;
        height: 100vh;
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
    }
    /**
     * Set styles for the button.
     */
    .led-toggle {
        background: #33aaee;
        color: #0066aa;
        border: 2px solid currentColor;
        border-radius: 8px;
        padding: 20px;
        font-weight: 600;
        font-size: 1.5em; /* 24px */
        width: 280px;
    }
</style>

Now we've made a our HTML and our CSS, we can set this file to show on root through localhost:8000. Update your index.js file with a line to display the HTML file when the user goes to the root. Another we should add is a line using our dist folder (which we'll get to) and display it as /static on the server.

// [...] Our Previous Server Code lives here...
// We have a dist directory, but use '/static' to fetch it.
app.use('/static', express.static('dist'));
// When the client is on the root, show the HTML file.
app.get('/', (req, res) => {
 res.sendFile(`${__dirname}/index.html`);
});
// Set the server to port 8000 and send the HTML from there.
http.listen(8000, () => {
 console.log('Server running on Port 8000');
});
// [...] Our Johnny-Five code lives here...

One other thing we can do is watch our files for any changes, and allow the code to reload upon change so we can continuously edit and see the changes instantly. For this, we'll be using a dependency called Nodemon.

To install Nodemon, we can add it to Yarn as a dependency.

yarn add nodemon

Now we need to update our start script in package.json. We'll be using Nodemon to execute babel-node and watch for any changes in the server code.

"start": "nodemon -e js --ignore index.compiled.js --ignore dist --exec babel-node index.js",
"test": "eslint ./ && flow"

Now, if we run our yarn start script, it will start our Johnny-Five as well as our server, and run on port 8000! So, if we jump on our browser (I'm using Google Chrome), we'll see our simple interface of a big fat button in the browser!

Now we've made our interface and our server, let's get on to making our client side scripts!

Installing Webpack and Writing the Client Side Scripts

Since we're writing in ES6 and Babel, we should use a bundler to compile all our ES6 code and make it work for browsers, especially since not a lot of ES6 is available in browsers yet! For this, we'll be using an awesome bundler called Webpack.

To install Webpack, we need to install it as a development dependency in Yarn.

yarn add --dev webpack

We now need to make a file called webpack.config.babel.js, and we'll be writing our Webpack configuration in ES6, since we can. This is what will take our code written for the browser using ES6 and compile it to browser-friendly code.

import path from 'path';
export default {
  entry: [
    './browser.js',
  ],
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist'),
  },
  module: {
    loaders: [
      {
        test: /\.js?$/,
        loader: 'babel-loader',
        exclude: /node_modules/,
      },
    ],
  },
  resolve: {
    extensions: ['.js'],
  },
  devtool: 'source-map',
};

Let's do a rundown of what the Webpack Config code above is doing, so we can make sense of what's happening here:

  • Our entry key is used to tell what file, or files, we want Webpack to take and bundle into our browser script.
  • output is used define specifics for the compiled file. In this case, we want it to be compiled into a file called bundle.js and output it into a new folder called dist.
  • Webpack uses the module feature to tell it what tools and dependencies to use on what files. In this case, we tell it to use babel-loader for JavaScript files.
  • resolve is used to find modules using aliases. For instance, modules imported via Node.JS are absolute to Webpack, so this helps resolve it. Another example is files called without a .js extension, and will accept the files without the extension.
  • devtool is an option in Webpack, to help debug files that are compiled. In this case, we want a source map along with our bundled JavaScript.

Now we've got an understanding of what Webpack does! The next thing we need to do is add a dev script. With this, we'll tell Webpack to bundle our client-side scripts, and when any changes take place, to re-bundle the scripts.

"start": "nodemon -e js --ignore index.compiled.js --ignore dist --exec babel-node index.js",
"dev": "webpack -w",
"test": "eslint ./ && flow"

Now let's start making our client-side scripts! Make a new file called browser.js and we'll start making our JavaScript for the client-side!

// @flow
/* eslint-disable no-console */
import 'babel-polyfill';
// Get the button for the LED toggle in the DOM
// eslint-disable-next-line no-undef
const ledToggle = document.getElementById('led-toggle');
// Set browser-side lightOn variable as true by default
let lightOn = true;

We start off by importing the babel-polyfill package, which will provide supporting scripts for ES6 features in an ES5 environment. Next, we define an ledToggle constant that will find the button in the HTML document by its ID. Finally, since the LED is on by default on the server's end, we'll set it as the default on the client end.

Now, while our server is running in one Terminal pane, let's open another pane and run yarn dev. Now our client-side JavaScript is bundled, with a source map, and placed in a new dist directory!

Now we need to make our client-side code to communicate with the server-side, so we can control our Nodebot! Let's take a deep dive into Websockets!

Using Socket.IO to Communicate between the Browser and Server

Let's take briefly about Websockets. Websockets allow us to communicate events between two points, specifically a server and a client. This is really useful since the communication is instant, and does not communicate with low latency unlike HTTP. A great example is chat applications, some use Websockets to communicate between users instantly over specific rooms on the service! We're going to be using Socket.IO to allow our client-side code to control our Nodebot.

We need to install two dependencies for websocket communication — the main socket.io library, and socket.io-client for the client-side.

yarn add socket.io socket.io-client

In our index.js file, we'll import socket.io to our server, and wrap our server instance into a new socket.io instance.

// @flow
/* eslint-disable no-console */
import { Board, Led } from 'johnny-five';
import express from 'express';
import { Server } from 'http';
import socketIO from 'socket.io';
// Make a new Express app
const app = express();
// flow-disable-next-line
const http = Server(app);
// Make a new Socket.IO instance and listen to the http port
const io = socketIO.listen(http);

Now when our Server is running, it will listen for socket communication on the client side on port 8000! Next thing we need to do is write events within our Johnny-Five code so when communication happens on the client-side our Nodebot will act accordingly!

// [...] Our previous code lives here...
// Set the server to port 8000 and send the HTML from there.
http.listen(8000, () => {
 console.log('Server running on Port 8000');
});
// Set `lightOn` to true as a default since our LED will be on
let lightOn = true;
// Make a new Board Instance
const board = new Board();
// When the board is connected, turn on the LED connected to pin 9
board.on('ready', function() {
  // [...] Our initial Johnny-Five code lives here...
  /**
   *
   * Server-Side Socket Events
   *
   * When the client-side has connected, output the status to console.
   * Then, send the current LED status to the client-side.
   *
   * When the LED is toggled from the client-side, update the lightOn variable.
   * Depending on the boolean sent, turn on or off the LED.
   *
   */
  io.on('connect', (socket) => {
    console.log('[socket.io] Client has connected to server!');
    socket.emit('light-state', lightOn);
    socket.on('light-toggle', (lightToggle) => {
      lightOn = lightToggle;
      // Log the current state of the LED.
      console.log(`[socket.io] Light is now ${lightOn ? 'on' : 'off'}.`);
      if (lightOn) {
        led.on();
      } else {
        led.stop().off();
      }
    });
  });
  // [...] Our REPL and Exit code live here...
});

With the code above, we'll know when the client has connected to the server with a log to our CLI console, and will send (emit) the current state of the LED to the client (in case our LED is off when we view our button in the browser!).

When the server receives an event called 'light-toggle', which we'll use for controlling the LED, it will set the lightOn variable to the value that has been sent, and logging if our LED is on or off. With the updated variable, this will turn our LED on or off when the button is pressed!

Now for the client-side sockets — we'll need to make events for telling the server the client-side has connected, and receive the current state of the LED, as well as events for when the button is pressed.

// @flow
/* eslint-disable no-console */
import 'babel-polyfill';
import socketIOClient from 'socket.io-client';
// Set socketIO to the window location given by the server
// eslint-disable-next-line no-undef
const io = socketIOClient(window.location.name);
// [...] Our previous client-side code lives here...
/**
 *
 * Client-Side Socket Events
 *
 * On connection, tell the server that the client side has connected.
 * Upon connection, a 'light-state' event may be sent from the server.
 * Set the lightOn variable to the current state on the server side.
 *
 */
io.on('connect', () => {
  console.log('[socket.io] Client has Connected!');
  // Get robot's current light state from the server side.
  io.on('light-state', (lightState) => {
    console.log(`[socket.io] Light is currently ${lightState ? 'on' : 'off'}.`);
    lightOn = lightState;
    if (ledToggle && !lightOn) {
      ledToggle.innerHTML = 'Turn On LED';
    }
  });
});
/**
 *
 * If the LED toggle button is found in the DOM, check when it's clicked.
 * When clicked, update the lightOn boolean, then send the value down to the
 * server and update the robot's LED state.
 *
 */
if (ledToggle) {
  ledToggle.addEventListener('click', () => {
    if (lightOn) {
      lightOn = false;
      ledToggle.innerHTML = 'Turn On LED';
    } else {
      lightOn = true;
      ledToggle.innerHTML = 'Turn Off LED';
    }
    // Send new value down to the server.
    io.emit('light-toggle', lightOn);
  });
}

When setting up socket.io-client, we need to set it to our local server, which in this case would be http://localhost:8000. An simple way to do that is get the address from window.location.name, which will be the root URL for the server. We then tell the server, via websockets, that the client has connected, and will receive the current LED state, and update certain things if necessary.

If the LED toggle button exists in the HTML, we can then listen for when it's clicked. Every time it is clicked, it will update the client-side lightOn variable, as well as the button's text, and send the new value to the server, which will then turn the Nodebot's LED on or off!

Testing and Making a Production Version of The Server

Now that we've made all this come together, let's give our simple Nodebot interface a go. With our server running and Webpack compiling our code, let's head on over to http://localhost:8000 and toggle the LED on and off!

Another little thing you can do is using a browser in a tablet or smartphone to control the LED using the simple interface we've just made, provided your device is on the same Wi-Fi network your computer, running the Node server, is connected to.

Important Note: to access via your smartphone or tablet, make sure that you have allowed file sharing on your computer. You will then be able to view the web interface using your machine's hostname or IP address. If you're stuck on where to find your machine's IP address, use ifconfig on macOS or hostname -i on Linux systems.

Next thing we need to do is make sure our code is clean and has no errors with ESLint and Flow — simply run yarn test and it will check our code. We should get no errors! Yay!

Lastly, we need to make a production-ready, compiled version of our code. Firstly, we need to install a dependency called rimraf — which will delete our previous compiled code so we can make a clean build.

yarn add --dev rimraf

Next, all we need to do is make a build script in package.json which will compile the code and make it production ready.

"start": "nodemon -e js --ignore index.compiled.js --ignore dist --exec babel-node index.js",
"dev": "webpack -w",
"build": "rimraf dist
"test": "eslint ./ && flow"

Now, it's worth pointing out, this is just for practice, but also, when using our server code in production scenarios, we can't just leave babel-node running all the time as it's prone to memory leaks! Instead, we'll make babel compile the code and let that run continuously if we want to. We've also got Webpack to compile our client-side scripts as well.

The last thing we need to do is make a script to run the compiled code for our server. Simple make a production script in package.json and tell node to run our compiled code.

"start": "nodemon -e js --ignore index.compiled.js --ignore dist --exec babel-node index.js",
"dev": "webpack -w",
"build": "rimraf lib dist && babel index.js --out-file index.compiled.js --ignore browser.js && webpack browser.js",
"test": "eslint ./ && flow",
"production": "node index.compiled.js" 

Now if we run yarn production, our Server and Nodebot will be up and running, ready to serve our interface and toggle with!

Stuck with any of the above?

If you're stuck with any of the code above, don't worry — I have attached a Git repository containing the final code, so if you're unsure about anything, you can happily use my code as a reference.

Give Yourself a pat on the back!

Well done! You've just made a simple Nodebot with web controls over a Server! We've come a long way the last few lessons, and have learned a lot. So, we've reached the end of the basics with using JavaScript and Hardware, to understanding what JavaScript and Nodebots are, to making a Simple Nodebot, using current JavaScript techniques, and making a simple web app. Well done for making it so far!

We're not done yet...

You didn't honestly think that was the end of the series? Gosh no! We're going to take our code to the next level after this! We'll still be making simple Nodebots, but we'll also be using a JS Framework for our Interface and use it to make a small Web App, as well as using client-side application states to control our next Nodebot! It's going to be awesome!

Can't wait for the next part?

If you're really excited for the next part, you can get it early by pledging to my Patreon! With Patreon, you can get perks such as behind the scenes looks and early releases of creations and articles, as well as patron-only posts!

Alternatively, if you want to support my work, I also have a PayPal! Be sure to follow me on Twitter, Like my page on Facebook, or subscribe to my mailing list to get updates!

Code

Simple Nodebot
The full code for the Simple Nodebot in this series — feel free to use as a reference!

Schematics

Simple Nodebot LED Circuit
Simply remove the Button from the previous circuit, we only need the LED now!
First nodebot led 5b9nrdigmy

Comments

Similar projects you might like

React and Johnny-Five Traffic Lights

by Iain

  • 2,042 views
  • 0 comments
  • 5 respects

Basic Arduino + JavaScript (Workshop)

by Alex Glow

  • 11,877 views
  • 1 comment
  • 27 respects
Add projectSign up / Login