Introduction: Drive a Webpage in Real-time Using Arduino, SensorMonkey and Processing.js

Remote visualization of real-time sensor data.

This tutorial describes in detail how to use the free SensorMonkey service to push real-time sensor data from an Arduino to a webpage for visualization using Processing.js. No server-side coding or Ethernet shield is required. A standard, run of the mill Arduino will work perfectly. You'll also need a sensor to sample some values. I use an accelerometer, but anything will work (a potentiometer, a gyroscope, a tilt sensor, a temperature sensor, a light sensor etc.). If you don't have a sensor, it's still possible to follow the tutorial by sampling the floating input voltages on the Arduino's analog pins as a (somewhat) crude substitute.

After configuring the Arduino to sample sensor values, I use SensorMonkey to publish the data live over the Internet in real-time (Disclosure: I co-founded the company developing SensorMonkey). Using SensorMonkey, I can access the data from any device connected to the Internet and use it to drive a real-time webpage. Proxies, firewalls and NATs can all be traversed. Best of all, it works with standard Arduino boards (Unos, Duemilanoves etc.) and does not require an Ethernet shield. Instead, I use free software called Bloom to network-enable the Arduino and connect it to SensorMonkey. In this tutorial, I visualize the data using Processing.js.

UPDATE 26-06-2012: Non-Windows Users
As an alternative to Bloom for non-Windows users, I have uploaded a Processing sketch, named SensorMonkeySerialNet, to our GitHub account. This sketch is a serial-to-network proxy that also serves Flash Socket Policy files inline. It can be used instead of Bloom in Step 3 for users running Mac OS or Linux.

Step 1: Gathering Materials

The following combination of hardware and software is required to complete this tutorial:

Hardware:

- Arduino (I use an Uno but older boards such as a Duemilanove will work fine)
- USB cable to connect Arduino to host computer
- Analog sensor (I use a ADXL335 accelerometer)
- Assorted wires to connect your sensor to the Arduino

Software:

- Arduino development environment (http://www.arduino.cc)
- Free account on SensorMonkey.com (login with your existing Facebook account)
- Bloom (serial port to TCP/IP socket redirector for Microsoft Windows)
- Processing.js

Step 2: Connect Arduino and Upload Sketch

If you have not done so already, you should take the time to familiarize yourself with the basic operation of an Arduino by reading the Getting Started guide on the main Arduino website. In particular, make sure you have downloaded and installed the Arduino development environment and that you are able to upload sketches to the board. The Arduino will be assigned a serial port when connected to the host computer. If using Windows, you can determine the assigned serial port by opening Device Manager and expanding the Ports (COM & LPT) section. You should see the Arduino listed underneath (in my case, the Arduino has been assigned to COM8).

I have connected my ADXL335 accelerometer to the Arduino as shown (image taken from http://bildr.org). I am going to sample analog-to-digital (ADC) pins 0, 1 and 2 on the Arduino at regular intervals and write their values to the serial port. To do this, I upload the following sketch to the Arduino's microcontroller using the development environment:


void setup() {
  Serial.begin( 9600 );    // Open the serial port.
}

void loop() {
  unsigned int x = analogRead( 0 );    // Read 10-bit x-axis accelerometer on ADC pin 0.
  unsigned int y = analogRead( 1 );    // Read 10-bit y-axis accelerometer on ADC pin 1.
  unsigned int z = analogRead( 2 );    // Read 10-bit z-axis accelerometer on ADC pin 2.

  // Write synchronization bytes to serial port to act as starting markers for each 'packet'.
  Serial.write( 0xA5 );
  Serial.write( 0x5A );

  // Write x-axis accelerometer to serial port as 16-bit unsigned integer in big-endian format.
  Serial.write( highByte( x ) );    // Most significant byte (MSB).
  Serial.write( lowByte( x ) );    // Least significant byte (LSB).

  // Write y-axis accelerometer to serial port as 16-bit unsigned integer in big-endian format.
  Serial.write( highByte( y ) );
  Serial.write( lowByte( y ) );

  // Write z-axis accelerometer to serial port as 16-bit unsigned integer in big-endian format.
  Serial.write( highByte( z ) );
  Serial.write( lowByte( z ) );

  delay( 20 );    // Add a delay of 20ms to give a sampling rate of approximately 50Hz.
}


The ADC pins have a 10-bit resolution (0 to 1023 inclusive) so I encode them as 16-bit unsigned integers in big-endian format before sending them over the serial port. Depending on the sensor(s) you are using, you can choose to sample more or less of the ADC pins. In my case, the ADXL335 accelerometer measures acceleration along three orthogonal axes: x, y and z. Hence, I sample the three corresponding ADC pins: 0, 1 and 2 respectively.

Finally, you can alter the sampling rate of the sketch by increasing or decreasing the delay as required. For sensors that do not change very often (e.g. a temperature sensor) you will likely want to increase the delay to sample at a slower rate. Setting it to 100 would sample 10 times per second (or 10Hz) for example.

Step 3: Download and Install Bloom

Before I can connect the Arduino to SensorMonkey, I need to map the serial port assigned to the device to a TCP/IP socket. To do this, I download and install Bloom from the SensorMonkey support page.

Bloom is a serial port to TCP/IP socket redirector for Microsoft Windows. It comes with a fairly comprehensive help manual (which I would encourage you to read), but the basic operation is very simple:

- Run Bloom from the Windows Start menu
- Configure serial port settings for the Arduino and choose a TCP/IP port for Bloom to listen on
- Set a polling frequency to (approximately) match the sampling rate of the Arduino's sketch
- Press 'Start'

Bloom will listen for incoming connections on the specified TCP/IP port. When a connection is accepted, Bloom will open the serial port and transfer data back and forth between the TCP/IP socket and the serial port, allowing SensorMonkey to connect to the Arduino as if it were a networked device with an Ethernet shield.

I use the following settings:

- TCP/IP port: 20000
- Polling frequency: 50
- Serial port: COM8
- Baud rate: 9600
- Data bits: 8
- Parity bit: None
- Stop bits: 1
- Flow control: None

The choice of TCP/IP port is arbitrary (you can choose whatever you like, as long as it's in the range 1024 to 49151, inclusive, and not already in use). Also, please bear in mind that your serial port will be different depending on what your Arduino was assigned.

For operating systems other than Windows, you can download an alternative to Bloom (typically referred to as a serial-to-network proxy) from our GitHub account. The sketch, named SensorMonkeySerialNet, runs in Processing on Mac OS and Linux. Please follow the instructions in the project's README file.

Step 4: Login to SensorMonkey

You can login to SensorMonkey using your existing Facebook account by clicking the 'Login with Facebook' button in the top-right corner of the page.

You will be prompted to grant permission for the SensorMonkey application to access your Facebook account. Once you have done so, you will be assigned a personal namespace (a streaming 'sandbox' for your sensors) as well as public and private keys to access your namespace from within a webpage. You will need your public key for Step 6. You can find it by clicking the 'Namespaces' link at the top of the page.

Once logged in, you can access the web-based control panel through the 'Sensors' link at the top of the page. The control panel is where you will connect to the Arduino and publish sensor data live over the Internet.

Step 5: Publish Sensor Data

After logging into SensorMonkey and opening my control panel, I'm going to add an entry for the Arduino named "My Arduino". By clicking on the newly added entry, I can configure the connection parameters; namely, the IP address and port number where the device can be found.

Recall from Step 3 that I am using Bloom to map the Arduino's serial port to TCP/IP port 20000 on my local machine. So, I enter a port number of 20000 and an IP address of 127.0.0.1 (the local loopback address).

I also need to specify a format description file that tells SensorMonkey how to parse and interpret the data being sent by the Arduino. In Step 2, I presented the sketch used to sample the accelerometer that was compiled and uploaded to the Arduino's microcontroller using the development environment. To match the data sent by the sketch, I use the following format description file:


<bytestream>
    <format endian="big">
        <constant>A5</constant>
        <constant>5A</constant>
        <variable type="u16">Accelerometer X</variable>
        <variable type="u16">Accelerometer Y</variable>
        <variable type="u16">Accelerometer Z</variable>
    </format>
</bytestream>


Note that I have specified big-endian format (<format endian="big">) and have added variables representing the three axes sampled by the accelerometer: x, y and z. The type of these variables is "u16", which is short-hand for 'Unsigned 16-bit Integer'. Many different types of variables are supported; you can find more information on the SensorMonkey support page.

The main point to realize here is that you just need to specify a format description file that matches the data being sent by your Arduino over the serial port. Depending on the sensor(s) that you are using, you may need to add more or less variables to your format description file. Make sure to give them descriptive names so you know what each variable is measuring.

After clicking 'Connect', I navigate to the 'Stream' tab, select the three accelerometer variables, choose a stream type of 'Public', and click 'Publish'. The sensor data is now being streamed live over the Internet as a public stream in my personal namespace.

In the next step, I will write a simple HTML webpage to connect to my namespace, subscribe to my stream, and visualize the data in real-time using Processing.js.

Step 6: Graph Data Using Processing.js

In the final (and best!) part of this tutorial, I'm going to create a simple webpage to view the output from my Arduino that is now being streamed live over the Internet using SensorMonkey (I have downloaded the latest Processing.js library - 1.3.6 at the time of writing - and placed it in the same directory as the webpage). You'll need to edit the code below to match the variables being streamed by your Arduino (unless you have copied my accelerometer setup exactly):

(Important! You must replace YOUR_NAMESPACE and YOUR_PUBLIC_KEY in the code below with those assigned to you when you login to SensorMonkey)

--------------------------------------------------------------------------------
<!DOCTYPE html>
<html>
<head>
    <title>Drive a webpage in real-time using Arduino, SensorMonkey and Processing.js</title>
    <script type="text/javascript" src="http://sensormonkey.eeng.nuim.ie/socket.io/socket.io.js"></script>
    <script type="text/javascript" src="http://sensormonkey.eeng.nuim.ie/js/client.min.js"></script>
    <script type="text/javascript" src="processing-1.3.6.js"></script>
    <style type="text/css">
        .sensor-name {
            text-align: center;
            width: 300px;
        }
        canvas {
            border: 1px solid grey;
        }
    </style>
</head>
<body onload="setTimeout( run, 100 );">
    <div class="sensor-name">Accelerometer X</div>
    <canvas data-processing-sources="Graph.pde" id="AccelX"></canvas>
    <div class="sensor-name">Accelerometer Y</div>
    <canvas data-processing-sources="Graph.pde" id="AccelY"></canvas>
    <div class="sensor-name">Accelerometer Z</div>
    <canvas data-processing-sources="Graph.pde" id="AccelZ"></canvas>
    <script type="text/javascript">
        function run() {
            var accelXGraph = Processing.getInstanceById( "AccelX" );
            var accelYGraph = Processing.getInstanceById( "AccelY" );
            var accelZGraph = Processing.getInstanceById( "AccelZ" );

            accelXGraph.setColor( 255, 0, 0, 100 );    // Red.
            accelYGraph.setColor( 0, 128, 0, 100 );    // Green.
            accelZGraph.setColor( 0, 0, 255, 100 );    // Blue.

            // 1. Connect to SensorMonkey
            // 2. Join namespace
            // 3. Subscribe to stream
            // 4. Listen for 'publish' and 'bulkPublish' events

            var client = new SensorMonkey.Client( "http://sensormonkey.eeng.nuim.ie" );
            client.on( "connect", function() {
                client.joinNamespace( "YOUR_NAMESPACE", "YOUR_PUBLIC_KEY", function( e ) {
                    if( e ) {
                        alert( "Failed to join namespace: " + e );
                        return;
                    }

                    client.subscribeToStream( "/public/My Arduino", function( e ) {
                        if( e ) {
                            alert( "Failed to subscribe to stream: " + e );
                            return;
                        }

                        client.on( "publish", function( name, fields ) {
                            if( name === "/public/My Arduino" ) {
                                accelXGraph.update( fields[ "Accelerometer X" ] );
                                accelYGraph.update( fields[ "Accelerometer Y" ] );
                                accelZGraph.update( fields[ "Accelerometer Z" ] );
                            }
                        } );

                        client.on( "bulkPublish", function( name, fields ) {
                            if( name === "/public/My Arduino" ) {
                                for( var i = 0, len = fields[ "Accelerometer X" ].length; i < len; i++ ) {
                                    accelXGraph.update( fields[ "Accelerometer X" ][ i ] );
                                    accelYGraph.update( fields[ "Accelerometer Y" ][ i ] );
                                    accelZGraph.update( fields[ "Accelerometer Z" ][ i ] );
                                }
                            }
                        } );
                    } );
                } );

                client.on( "disconnect", function() {
                    alert( "Client has been disconnected!" );
                } );
            } );
        }
    </script>
</body>
</html>
--------------------------------------------------------------------------------

Without going into too much detail (you can find more information about the JavaScript client API here) the basic workflow is as follows:

- Import client
- Connect to SensorMonkey
- Join namespace
- Subscribe to stream
- Listen for 'publish' and 'bulkPublish' events

To graph the data, I'm using the following Processing.js sketch (save this to a file called Graph.pde and place it in the same directory as the webpage above):

--------------------------------------------------------------------------------
int xPos = 0;       // Horizontal coordinate used to draw the next data point.
int yMin = 0;       // Minimum expected data value.
int yMax = 1023;    // Maximum expected data value.
color c;            // Stroke color used to draw the graph.

// Sets the stroke color used to draw the graph.
void setColor( int r, int g, int b, int a ) {
  c = color( r, g, b, a );
}

void setup() {
  size( 300, 200 );
  frameRate( 50 );
  setColor( 255, 0, 0, 100 );
  drawGrid();
}

void draw() {}    // Empty draw() function.

void drawGrid() {
  int h = height;
  int w = width;

  background( 255 );

  stroke( 127, 127, 127, 127 );

  // Draw horizontal lines.
  line( 0, h / 4, w, h / 4 );
  line( 0, h / 2, w, h / 2 );
  line( 0, h * 3 / 4, w, h * 3 / 4 );

  // Draw vertical lines.
  line( w / 4, 0, w / 4, h );
  line( w / 2, 0, w / 2, h );
  line( w * 3 / 4, 0, w * 3 / 4, h );

  // Draw labels.
  fill( 0 );
  text( str( yMin ), 5, h - 5 );
  text( str( yMax ), 5, 12 );
}

void update( float data ) {
  // When we reach the edge of the screen, wrap around to the beginning.
  if( xPos >= width ) {
    xPos = 0;
    drawGrid();
  }

  // Graph the data point and increment the horizontal coordinate.
  data = map( data, yMin, yMax, 0, height );
  stroke( c );
  line( xPos, height, xPos, height - data );
  xPos++;
}
--------------------------------------------------------------------------------

In your case, depending on the sensor(s) that you are streaming, you may need more or less graphs in your webpage. You can edit the Graph.pde file if you need to increase/decrease the size of the graphs, the range of data values that can be plotted, the frame rate etc. Just remember to include the Graph.pde file once for every variable that you want to plot (inside a <canvas> element) and name them accordingly (e.g. <canvas data-processing-sources="Graph.pde" id="TemperatureSensor"></canvas>). Then, you just need to get a reference to the graph (obtained by calling the Processing.getInstanceById() method) and use the update() function to plot new data points received in the "publish" and "bulkPublish" event handlers.

That's it! I now have an accelerometer driving a webpage in real-time using Arduino, SensorMonkey and Processing.js. I can host the webpage on a public webserver and direct people to view the link on any device with a HTML5 compatible web-browser. Thanks for reading and look out for further instructables showing more advanced use cases and projects in the near future.