Our next tutorial will explain how to setup a web server on your Raspberry Pi so that you can create web pages displaying real time sensor data. We will be installing a framework called Express (https://expressjs.com) that makes it easy to create a web server application that responds to browser http requests returning the relevant web pages, sensor data etc.

Installing Express.js

Installing Express and getting started with our first application is made easy with the use of express-generator. This tool when run will not only download the Express software, but also all of the dependent software packages. It will also create a skeleton web server application, including creating a file system structure for storing web pages, including css, javascript and image files.

First make sure you are in your automation directory, in my case its called myAutomation, then run the following command to install version 4 of the express-generator.

npm install express-generator@4.x

This downloads an executable file ./node_modules/.bin/express . We will use this to create an express application using the following command. (Note that ejs is a framework for dynamically creating web page html code on the server. It is one of several options that Express supports).

./node_modules/.bin/express –ejs

The screen shot below show the resulting screen output. When prompted with the question, destination is not empty, continue? [y/N], respond with Y.

As you can see from the above screen shot, the express-generator has created several directories and files which will become of our web server application that we will describe shortly. It also created a file package.json which is a list of all of the execution files described below which we need to install to run our express application.

express : the express execution engine
cookie-parser : a module that makes it easier to access cookies associated with web pages. (https://www.npmjs.com/package/cookie-parser)
debug : a module that makes it easier to debug your application (https://www.npmjs.com/package/debug)
ejs : a framework for dynamically creating web pages on your server (https://ejs.co)
http-errors : a module that makes it easy to create web page error messages (https://www.npmjs.com/package/http-errors)
morgan : a module that can be used to create logs that can help you analyze web site requests.(https://www.npmjs.com/package/morgan)

It’s easy to install all of these, simply run the npm install command shown below which will interrogate the package.json file and install the correct versions of the above packages for us.

npm install

Now that we have installed all the required Express files lets do a quick check that everything is working. The express-generator created two application files, bin/www and app.js that are a bare bones express application. To make sure the installation was a success simply run the command node ./bin/www . Then open up a web browser on your RaspberryPi and enter the url localhost:3000. Below shows the Terminal window along-side the web browser response that shows everything is working. The web page should show the Express Welcome message.

Building your first Express application

Now its time to create our first Express web application. If you are new to Express and the EJS templating engine then I suggest you checkout the post in our Articles. The post ‘Express primer‘ will explain the structure of an Express application. In this tutorial we will create a web page that can be accessed through url ‘localhost:8080/sensorTable/view‘. The table, shown below, will list the description, data and trueText/falseText parameters for all of the sensors in our application. e.g.

DescriptionDataStatus
Side Gate 0CLOSED
Front Door 1OPEN

There are several techniques for dynamically creating web pages. The Express/EJS approach enables you to create HTML elements within the server application using data from our applications sensor object before sending the final web with the table built to the browser. Though this approach is OK for some applications, it has limitations (e.g. if the user wanted to sort/filter data in the table it would require rebuilding the web page on the server and transmitting to the browser). The approach I have taken is to replicate our applications sensors object to a javascript object within the browser. We can then use javascript running in the browser to dynamical create and modify HTML elements using the jquery framework. This approach offers more flexibility, and is a tried and tested approach of web developers.

Below is a diagram and outline description of each of the files that make up our Express application. Boxes in grey are the new files we will be creating. Boxes in yellow are part of our application that we built in previous tutorial ‘Reading sensor messages’. The red text shows how we manage to get the sensor object from our server application to the browser. Our applications sensors object is first converted into a JSON encoded string within the myAutomationApp.js module and assigned to an Express variable res.locals ( i.e. res.locals.sensorsString = JSON.stringify(sensors) ). Variables assigned to res.locals can then be accessed in the EJS file pageHeader.ejs. The EJS code ‘var sensors = <%- sensorsString %>‘ creates the browser javascript object sensors that can now be used within the browser’s javascript file ./public/javascripts/sensorTable.js to dynamically create the table of sensor data in the final HTML page rendered in the browser.

Express + EJS Web Server Application

We will need to modify one file (main.js) and create 6 new files as follows,

Modify main.js (modified): The first step is to modify the main.js file that we wrote back in the ‘Reading Sensor Messages’ tutorial. Change line 32 which had the code main.activate() to match the code below. The reason for this change is that main.js will no longer be the point to launch our application. Instead we will launch our application with the new file myAutomationApp.js. As you will see below, we will load our main.js module into myAutomationApp.js and activate from there.

module.exports = main;

myAutomationApp.js (new) : This will be our primary program that we will run with the command node myAutomationApp.js. It will load and activate our module main.js and setup a web server that will handle browser requests to view web pages.

./routes/sensorTable.js (new) : This is an Express router module. Router modules interpret the URL sent from the web browser and respond by sending the appropriate response. The response could be a specific web page, a stream of json encoded data etc. Router modules are stored in the routes directory of your application. Our router will be designed to respond to the browser URL localhost:3000/sensorTable/views be sending the web page sensorTable.ejs.

./views/sensorTable.ejs (new) : If you remember when we installed Express we used the option –ejs. EJS is a simple templating language that lets you generate HTML markup on your server with plain JavaScript. EJS files are HTML pages that contains special coding enclosed with brackets <% and %>. Check out their web site https://ejs.co for documentation. The EJS engine executes this code to create the final HTML page. In this file we have just one EJS instruction <% include pageHeader %> which is an instruction to insert the code from file pageHeader.ejs at this location. The rest of the HTML mark up is straight forward, creating a table without any rors, as these will be added dynamicaly once the page has loaded into the browser. One point to be aware of is how the css and javascript files for the page are referenced with pathnames ‘/stylesheets/style.css’ and ‘javascripts/sensorTable.js’. These path names are incomplete as they don’t specify what they are relative to. We need to look back at our myAutomationApp.js file, line number 26 app.use(express.static(path.join(__dirname, 'public'))); to understand where these files are located. The middleware express.static() is used to define the location of static files such as css, javascript and images that are referenced in your EJS file. Though these files can be stored anywhere on your Raspberry Pi, for security reasons it is typical to store them in a subdirectory of your application called public. This subdirectory was created when you installed the Express software. We create the full path name using the utility method path.join(). This joins together __direname, which is the directory where your application myAutomationApp.js resides, with /public. This provides the root location for static files (e.g. in my example it is /home/pi/myAutomation/public). Thus for my application EJS adds the static root directory to locate the css file in /home/pi/myAutomation/public/stylesheets/style.css.

./views/pageHeader.ejs (new) : EJS allows us to create template files that can be included into multiple web pages. This template file has two goals. First it loads the latest version of jquery from the Google CDN repository. Then it runs a small piece of JavaScript code (var sensors = <%- sensorString %> . The variable sensorString was created in our myAtomationApp.js module. Though the variable was called res.locals.sensorString , we don’t need the full definition because EJS by default searches the Express variable res.locals . If you remember sensorString was a JSON encoded string version of our applications sensor object. Thus the assignment simply recreates this javascript sensor object within our HTML page. It is important that pageHeader.ejs is loaded within the <head> of your main EJS page so that the sensor variable is available to your page’s jasvascript that is loaded at the end of the <body> section.

./public/javascripts/sensorTable.js (new) : This is standard javascript that uses jquery to create our table. We iterate over the sensor object to identify each of our sensors. Then create table <td> elements that contain the relevant sensor parameters e.g. side_gate.description and side_gate.data.

./public/stylesheets/style.css (new) : This is standard css that we have defined to style our table. .

Code listings for our 6 new files.

./myAutomationApp.js

This is the primary program that when launched will start our web server and activate our application module main.js.

#!/usr/bin/env node

var http			= require('http');
var express		= require('express');
var path			= require('path');

var main     					= require('./main');
var indexRouter				= require('./routes/index');
var sensorTableRouter	= require('./routes/sensorTable');

var sensors         = require("./myModules/sensors");

// ---------- Start my node.js app -------------------------------------

main.activate();

// ---------- Setup Express server object ------------------------------

var app  = express();
var port = '8080';
app.set('port', port);

var server = http.createServer(app);

// ---------- Configure express options and parameters -----------------

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');

// set environment for HTML debug messages.
// 'development' results in error.stack displayed on HTML error page
// 'production' simply returns HTML error page with code 500
app.set('env', 'development');

// static paths for web pages
app.use(express.static(path.join(__dirname, 'public')));


// ---------- Middleware functions -------------------------------------
// middleware app making sensor objects available to web page rendering engine EJS

var mySensorObjects = function(req, res, next){
	res.locals.sensors = sensors;
	res.locals.sensorsString = JSON.stringify(sensors);	 // convert object to string
	next();
}	

// ---------- Route http requests --------------------------------------

app.use(mySensorObjects);

app.use('/', indexRouter);
app.use('/sensorTable', sensorTableRouter);


// catch 404 and forward to error handler
app.use(function(req, res, next) {
  var err = new Error('Not Found');
  err.status = 404;
  next(err);
});

// error handler
app.use(function(err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  console.log("ENV = " + req.app.get('env'));
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // render the error page
  res.status(err.status || 500);
  res.render('error');
});

// ---------- http Error Handlers --------------------------------------

server.on('error', onError);
server.on('listening', onListening);

 /**
 * Event listener for HTTP server "listening" event.
 */

function onListening() {
  var addr = server.address();
  var bind = typeof addr === 'string'
    ? 'pipe ' + addr
    : 'port ' + addr.port;
  console.log('Listening on ' + bind);
}

/*
 * Event listener for HTTP server "error" event.
 */

function onError(error) {
  if (error.syscall !== 'listen') {
    throw error;
  }

  var bind = typeof port === 'string'
    ? 'Pipe ' + port
    : 'Port ' + port;

  // handle specific listen errors with friendly messages
  switch (error.code) {
    case 'EACCES':
      console.error(bind + ' requires elevated privileges');
      process.exit(1);
      break;
    case 'EADDRINUSE':
      console.error(bind + ' is already in use');
      process.exit(1);
      break;
    default:
      throw error;
  }
}

// ---------- Start Web Server -----------------------------------------

server.listen(port);

// ---------- Done -----------------------------------------------------

module.exports = app;

(code explanation details to follow)

./routes/sensorTable.js

This router is designed to respond to url with path /sensorTable/view

var express = require('express');
var router = express.Router();

/* Router for web page. */
router.get('/view', function(req, res, next) {
  res.render('sensorTable');
});

module.exports = router;

gdfgdfgdfgfddfgdf

./views/sensorTable.ejs

This EJS file is is the HTML page that will be rendered in response to the url with path /sensorTable/view

<!DOCTYPE html>
<html>
	
  <head>
    <title>Sensor Data Table</title>
    <link rel='stylesheet' href='/stylesheets/style.css' />
    <% include pageHeader %>
  </head>
  
  <body>
		
    <h1>Sensor Data Table</h1>
    
    <table id="sensorDataTable">
			<tr>
				<th>Description</th>
				<th>Data</th>
				<th>Status</th>
			</tr>
			
    </table>  
    
    <script src = "/javascripts/sensorTable.js"></script>
  </body>
</html>

(code explanation details to follow)

./views/pageHeader.ejs

<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>

<script>
	var sensors = <%- sensorsString %>
</script>

(code explanation details to follow)

./public/javascripts/sensorTable.js

Javascript file

for(var sensorObj in sensors){
	
	var sensor = sensors[sensorObj];
		
	var row = $('<tr>');
	row.append($('<td>').text(sensor.description));	
	row.append($('<td>').text(sensor.data));
	row.append($('<td>').text(sensor.data ? sensor.trueText : sensor.falseText));
	
	$("#sensorDataTable").append(row);	
}	

(code explanation details to follow)

./stylesheets/style.css

css file

body {
  padding: 50px;
  font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
}

a {
  color: #00B7FF;
}

table {
	table-layout: fixed;
	border-collapse: collapse;
	border: 1px solid grey;


}

th,td {
	border: 1px solid grey;
	text-align: center;
}

(code explanation details to follow)

Leave a Reply

Your email address will not be published. Required fields are marked *