by Alexandre Alapetite on 2012-06-23; updated on 2013-07-20

Raspberry Pi with Node.js and Arduino

Can Arduino and Raspberry Pi be friends?

Live demo (not always running… Backup link)
français

Introduction

I had an Arduino Uno R3 together with the Arduino Ethernet Shield R3, but I never got the Ethernet Shield to be stable over several days, even for my simple temperature project.

Enters Raspberry Pi, a tiny inexpensive (~27€) single-board computer that runs Linux, is stable, cheaper than the Arduino Ethernet Shield alone (~32€), and only slightly more expensive than an Arduino Uno (~22€).

On the other hand, to interact with the physical and analogue world, the Raspberry Pi only has some unprotected digital GPIO pins, not safe for a software guy like me. Few extension cards are available so far for the Raspberry Pi, they can be more expensive than an Arduino Uno, and they do not have the same amount of libraries and community as Arduino.

The idea: combine Raspberry Pi for the high-level software with Arduino for the lower-level electronics, through a simple USB connection!

Furthermore, I want the Raspberry Pi to have an ultra-light Web server that can both be event-driven by the Arduino and send requests to the cloud.
My selection: Node.js.

This makes a coherent plug-&-play system with good documentation for the different parts.


Content


Architecture

[Raspberry Pi / USB / Arduino]

I use a tablet or smartphone charger ETA-P10X (5V, 2A) to power the Raspberry Pi via micro-USB (5V, 500mA or more), which is also connected to the Internet via an Ethernet cable, and to the Arduino Uno via USB. The main storage for the Raspberry Pi is an SD Card (Samsung 32GB SDHC Class 10), out of which I use less than 2GB.
The Arduino is connected to a solderless breadboard, forming a basic electronic circuit (voltage dividers) for simple temperature measurement (not detailed).
The Arduino Ethernet Shield is not used anymore.

Index

Preparing the Arduino

In the spirit of Node.js being event-driven and in order to save energy, the Arduino sleeps most of the time and when it wakes up (here, every 5 minutes), it sends some text that generates an event in Node.js.

With Node.js native language being JavaScript, it makes sense to send data from the Arduino to the Raspberry Pi using JSON text strings.

The following Arduino program is meant to measure two different thermistors (Temperature sensor with steel head) in order to report the temperature inside and outside.

It is outside the scope of this document to explain the electronic details, as the focus on the interaction between Arduino and Raspberry Pi.

TemperatureSerial.ino

#include <math.h>

unsigned long UpdateDelay = 1000UL * 60 * 5;	//Update frequency (5 minutes)
const byte Temperature1Pin = 0;	//Thermistor 1 (Outdoor)
const byte Temperature2Pin = 1;	//Thermistor 2 (Indoor)
const int Resistance1 = 9900;	//Ohms (measured from R10K of voltage divider 1)
const int Resistance2 = 9980;	//Ohms (measured from R10K of voltage divider 2)
const byte NbSamples = 8;	//Averaging

void setup()
{
	delay(1000);
	Serial.begin(9600);	//Start serial port
	pinMode(Temperature1Pin, INPUT);
	pinMode(Temperature2Pin, INPUT);
	digitalWrite(Temperature1Pin, LOW);
	digitalWrite(Temperature2Pin, LOW);
	analogRead(Temperature1Pin);
	analogRead(Temperature2Pin);
}

void loop()
{
	float rawADC1 = 0.0;
	float rawADC2 = 0.0;
	for (byte i = NbSamples; i > 0; i--)
	{//Averaging over several readings
		rawADC1 += analogRead(Temperature1Pin);
		rawADC2 += analogRead(Temperature2Pin);
		delay(100);
	}
	rawADC1 /= NbSamples;
	rawADC2 /= NbSamples;

	//Sending a JSON string over Serial/USB like: {"ab":"123","bc":"234","cde":"3546"}
	Serial.println("{\"adc\":\"" + String((long)round(100.0 * rawADC1)) +
			"\", \"celsius\":\"" + String((long)round(100.0 * thermistor(rawADC1, Resistance1))) +
			"\", \"adc2\":\"" + String((long)round(100.0 * rawADC2)) +
			"\", \"celsius2\":\"" + String((long)round(100.0 * thermistor(rawADC2, Resistance2))) +
			"\"}");

	delay(UpdateDelay);
}

float thermistor(float rawADC, float rSeries)
{//http://arduino.cc/playground/ComponentLib/Thermistor2
	//This method is not very advanced
	long resistance = (1024 * rSeries / rawADC) - rSeries;
	float temp = log(resistance);
	temp = 1 / (0.001129148 + (0.000234125 * temp) + (0.0000000876741 * temp * temp * temp));
	return temp - 273.15;	//Kelvin to Celsius
}

Then plug a standard USB cable type A-B between the Raspberry Pi (host) and the Arduino (device). There should be no need of additional power supply to the Arduino.

Index

Preparing the Raspberry Pi

There are already multiple Linux distributions for the Raspberry Pi, nicknamed RasPi. I have chosen the last Raspbian, a Debian Linux customised for the Raspberry Pi, which comes with a nice raspi-config assistant at startup that takes care of expending the system to the full size of the SD Card, etc.

Just follow the instructions to prepare the SD Card, and after the first boot, perform the following commands:

sudo nano /etc/ssh/sshd_config
	PermitRootLogin no	#Change this entry to forbid remote SSH root access

sudo apt-get update && sudo apt-get dist-upgrade && sudo apt-get autoremove

sudo apt-get install localepurge	#To reclaim a lot of disc space by removing unused languages
sudo localepurge

sudo apt-get install fail2ban	#To add a bit of safety when the Raspberry Pi is visible from Internet
sudo fail2ban-client status
Index

Preparing Node.js

Note.js is an ultra-light open-source server-side JavaScript platform based on Google’s V8 engine (like in Chrome). To install it:

sudo apt-get update && sudo apt-get install nodejs npm

However, at the time of the writing, the versions of Node.js an npm available in the repositories were too old.
Here is my approach to install nodejs (version 0.10.6) and npm (version 1.12.18) from the upstream repository, after having checked for newer versions in:
http://nodejs.org/dist/latest/.

wget http://nodejs.org/dist/v0.10.6/node-v0.10.6-linux-arm-pi.tar.gz
tar xvzf node-v* && rm node-v*.gz
sudo mv node-v* /opt/node
sudo mkdir /opt/bin
sudo ln -s /opt/node/bin/* /opt/bin/

If it is not already the case, add /opt/bin/ to the list of default paths :

sudo nano /etc/profile
/etc/profile

	PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/opt/bin"

Make yourself two folders ~/public_html/ and ~/log/nodejs/ , move to ~/public_html/ where you will download 2 scripts:

mkdir -p /home/pi/public_html	//For the scripts
mkdir -p /home/pi/log/nodejs	//For some logs
cd /home/pi/public_html
wget http://alexandre.alapetite.fr/doc-alex/raspberrypi-nodejs-arduino/index.js
wget http://alexandre.alapetite.fr/doc-alex/raspberrypi-nodejs-arduino/arduinoTemperature.js

In Node.js, you must both write the Web server logic (what e.g. Apache does) as well as the Web page logic (what PHP does), all in JavaScript.
Here is my main file, in charge of listening for Web requests, serving dynamic responses, static files, and 404 errors:

index.js

"use strict";

function escapeHtml(text)
{
	return text.replace(/&/g, "&amp;")
		.replace(/</g, "&lt;")
		.replace(/>/g, "&gt;")
		.replace(/"/g, "&quot;")
		.replace(/'/g, "&#039;");
}

var fs = require('fs');
var os = require('os');
var path = require('path');
var util = require('util');

var serverSignature = 'Node.js / Debian ' + os.type() + ' ' + os.release() + ' ' + os.arch() + ' / Raspberry Pi';

function done(request, response)
{
	util.log(request.connection.remoteAddress + '\t' + response.statusCode + '\t"' + request.method + ' ' + request.url + '"\t"' +
		request.headers['user-agent'] + '"\t"' + request.headers['accept-language'] + '"\t"' + request.headers['referer'] + '"');
}

function serve404(request, response, requestUrl)
{//When a static file is not found
	response.writeHead(404,
	{
		'Content-Type': 'text/html; charset=UTF-8',
		'Date': (new Date()).toUTCString(),
		'Server': serverSignature
	});
	response.end('<!DOCTYPE html>\n\
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-GB" lang="en-GB">\n\
<head>\n\
<meta charset="UTF-8" />\n\
<title>404 Not Found</title>\n\
</head>\n\
<body>\n\
<h1>Not Found</h1>\n\
<p>The requested <abbr title="Uniform Resource Locator">URL</abbr> <kbd>' +
	escapeHtml(requestUrl.pathname) + '</kbd> was not found on this server.</p>\n\
</body>\n\
</html>\n\
');
	done(request, response);
}

function serveStaticFile(request, response, requestUrl)
{
	var myPath = '.' + requestUrl.pathname;
	if (myPath && (/^\.\/[a-z0-9_-]+\.[a-z]{2,4}$/i).test(myPath) && (!(/\.\./).test(myPath)))
		fs.stat(myPath, function (err, stats)
		{
			if ((!err) && stats.isFile())
			{
				var ext = path.extname(myPath);
				var mimes = { '.css': 'text/css', '.html': 'text/html', '.ico': 'image/x-icon', '.jpg': 'image/jpeg',
					'.js': 'application/javascript', '.json': 'application/json', '.png': 'image/png', '.txt': 'text/plain', '.xml': 'application/xml' };
				var modifiedDate = new Date(stats.mtime).toUTCString();
				if (modifiedDate === request.headers['if-modified-since'])
				{
					response.writeHead(304,
					{
						'Content-Type': ext && mimes[ext] ? mimes[ext] : 'application/octet-stream',
						'Date': (new Date()).toUTCString()
					});
					response.end();
				}
				else
				{
					response.writeHead(200,
					{
						'Content-Type': ext && mimes[ext] ? mimes[ext] : 'application/octet-stream',
						'Content-Length': stats.size,
						'Cache-Control': 'public, max-age=86400',
						'Date': (new Date()).toUTCString(),
						'Last-Modified': modifiedDate,
						'Server': serverSignature
					});
					fs.createReadStream(myPath).pipe(response);
				}
				done(request, response);
			}
			else serve404(request, response, requestUrl);
		});
	else serve404(request, response, requestUrl);
}

function serveHome(request, response, requestUrl)
{
	var now = new Date();
	response.writeHead(200,
	{
		'Content-Type': 'text/html; charset=UTF-8',
		'Date': now.toUTCString(),
		'Server': serverSignature
	});
	response.end('<!DOCTYPE html>\n\
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-GB" lang="en-GB">\n\
<head>\n\
<meta charset="UTF-8" />\n\
<title>Test of Node.js on Raspberry Pi</title>\n\
<meta name="robots" content="noindex" />\n\
<meta name="viewport" content="initial-scale=1.0,width=device-width" />\n\
<link rel="author" href="http://alexandre.alapetite.fr/cv/" title="Alexandre Alapetite" />\n\
</head>\n\
<body>\n\
<pre>\n\
Hello ' + request.connection.remoteAddress + '!\n\
This is <a href="http://nodejs.org/">Node.js</a> on <a href="http://www.raspberrypi.org/">Raspberry Pi</a> :-)\n\
It is now ' + now.toISOString() + '.\n\
</pre>\n\
<ul>\n\
<li><a href="./temperature">Temperature</a></li>\n\
<li><a href="index.js">Source code</a></li>\n\
</ul>\n\
</body>\n\
</html>\n\
');
	done(request, response);
}

var arduino = require('./arduinoTemperature.js');	//Connection with Arduino

function serveTemperature(request, response, requestUrl)
{
	var now = new Date();
	response.writeHead(200,
	{
		'Content-Type': 'text/html; charset=UTF-8',
		'Date': now.toUTCString(),
		'Server': serverSignature,
		'Last-Modified': temperatureResponse.dateLastInfo.toUTCString()
	});
	response.end(temperatureResponse.html);
	done(request, response);
}

var http = require('http');
var url = require('url');

var server = http.createServer(function (request, response)
{
	var requestUrl = url.parse(request.url);
	switch (requestUrl.pathname)
	{
		case '/': serveHome(request, response, requestUrl); break;
		case '/temperature': serveTemperature(request, response, requestUrl); break;
		default: serveStaticFile(request, response, requestUrl); break;
	}
}).listen(8080);

console.log('Node.js server running at %j', server.address());

You can test that it works so far by commenting out the line //var arduino = require… near the end of the file above, execute the following instruction, and browse to http://your-raspberry-ip.example:8080/

cd /home/pi/public_html
node index.js

Serial communication between Node.js and Arduino

Now we need Node.js to react when the Arduino is talking over USB. When connected via USB to the Raspberry Pi, the serial connection to the Arduino is detected as something like /dev/ttyACM0 and may vary slightly in your setup.
We need to add some serial communication capabilities to Node.js, as follows:

cd /home/pi/public_html
sudo npm install serialport

And here is the script that I use to react to JSON messages sent by Arduino (i.e. event-driven) by updating the temperatures.
It also forwards a copy of the new information to a dedicated Web server via an HTTP POST request, so that the little Raspberry Pi (furthermore on a home Internet connection) can avoid being the front-end Web server if necessary.

arduinoTemperature.js

"use strict";

var arduinoSerialPort = '/dev/ttyACM0';	//Serial port over USB connection between the Raspberry Pi and the Arduino

var fs = require('fs');
function writeFile(text)
{
	fs.writeFile('temperature.json', text, function(err)
	{
		if (err) console.warn(err);
	});
}

var os = require('os');
var serverSignature = 'Node.js / Debian ' + os.type() + ' ' + os.release() + ' ' + os.arch() + ' / Raspberry Pi B + Arduino Uno R3';

var postOptions =
{
	host: 'posttestserver.com',	//Change to your own server
	path: '/post.php',
	method: 'POST',
	headers:
	{
		'Content-Type': 'application/x-www-form-urlencoded',
		'Connection': 'close',
		'User-Agent': serverSignature
	}
};

var http = require('http');
function postData(s)
{//Standard HTTP POST request
	var myOptions = postOptions;
	postOptions.headers['Content-Length'] = s.length;

	var requestPost = http.request(myOptions, function(res)
	{
		res.setEncoding('utf8');
		res.on('data', function (chunk)
		{
			console.log(chunk);
		});
	});

	requestPost.on('error', function(e)
	{
		console.warn(e);
	});

	requestPost.write(s);
	requestPost.end();
}

var serialport = require('serialport');
var serialPort = new serialport.SerialPort(arduinoSerialPort,
{//Listening on the serial port for data coming from Arduino over USB
	parser: serialport.parsers.readline('\n')
});

var lastTemperatureIndoor = NaN;
var lastTemperatureOutoor = NaN;
var dateLastInfo = new Date(0);

var querystring = require('querystring');
serialPort.on('data', function (data)
{//When a new line of text is received from Arduino over USB
	try
	{
		var j = JSON.parse(data);
		lastTemperatureOutoor = j.celsius / 100.0;
		lastTemperatureIndoor = j.celsius2 / 100.0;
		dateLastInfo = new Date();
		writeFile('{"outdoor":"' + lastTemperatureOutoor + '","indoor":"' + lastTemperatureIndoor + '"}');
		//Forward the Arduino information to another Web server
		postData(querystring.stringify(j));
	}
	catch (ex)
	{
		console.warn(ex);
	}
});


function colourScale(t)
{//Generate an HTML colour in function of the temperature
	if (t <= -25.5) return '0,0,255';
	if (t <= 0) return Math.round(255 + (t * 10)) + ',' + Math.round(255 + (t * 10)) + ',255';
	if (t <= 12.75) return Math.round(255 - (t * 20)) + ',255,' + Math.round(255 - (t * 20));
	if (t <= 25.5) return Math.round((t - 12.75) * 20) + ',255,0';
	if (t <= 38.25) return '255,' + Math.round(255 - (t - 25.5) * 20) + ',0';
	return '255,0,0';
}

function temperatureResponse()
{
	return {
		'lastTemperatureIndoor': lastTemperatureIndoor,
		'lastTemperatureOutoor': lastTemperatureOutoor,
		'html': '<!DOCTYPE html>\n\
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-GB" lang="en-GB">\n\
<head>\n\
<meta charset="UTF-8" />\n\
<meta http-equiv="refresh" content="300" />\n\
<title>Temperature - Arduino - Raspberry Pi</title>\n\
<meta name="keywords" content="Temperature, Arduino, Raspberry Pi" />\n\
<meta name="viewport" content="initial-scale=1.0,width=device-width" />\n\
<link rel="alternate" type="application/json" href="temperature.json"/>\n\
<meta name="robots" content="noindex" />\n\
<style type="text/css">\n\
html, body {background:black; color:white; font-family:sans-serif; text-align:center}\n\
.out {font-size:48pt}\n\
.in {font-size:36pt}\n\
.r, .sb {bottom:0; color:#AAA; position:absolute}\n\
.r {left:0.5em; margin-right:5em; text-align:left}\n\
.sb {right:0.5em}\n\
a {color:#AAA; text-decoration:none}\n\
a:hover {border-bottom:1px dashed}\n\
</style>\n\
</head>\n\
<body>\n\
<h1>Temperature somewhere</h1>\n\
<p>Outdoor<br /><strong class="out" style="color:rgb(' + colourScale(lastTemperatureOutoor) + ')">' +
	(Math.round(lastTemperatureOutoor * 10) / 10.0) + '°C</strong></p>\n\
<p>Indoor<br /><strong class="in" style="color:rgb(' + colourScale(lastTemperatureIndoor) + ')">' +
	(Math.round(lastTemperatureIndoor * 10) / 10.0) + '°C</strong></p>\n\
<p>' + dateLastInfo.toISOString() + '</p>\n\
<p class="r"><a href="http://alexandre.alapetite.fr/doc-alex/raspberrypi-nodejs-arduino/" title="Based on">Arduino + Raspberry Pi</a></p>\n\
</body>\n\
</html>\n\
',
	dateLastInfo: dateLastInfo
	};
}

module.exports.temperatureResponse = temperatureResponse;

Running Node.js as a server

Now the little Raspberry Pi is ready to run Node.js as a server, which means that Node.js should not stop when you log off. To do so, there are different ways, but here is a simple approach (will not auto-restart upon crash, or when the Raspberry Pi is rebooted):

cd /home/pi/public_html/
nohup node /home/pi/public_html/index.js >> /home/pi/log/nodejs/console.log 2>&1 &

You can then kill the server by doing:

killall node

If your Node.js is crashing one way or another, you can test regularly and reload it. Similarly, if your Raspberry Pi loses the Ethernet connection, it is possible to test regularly and do something such as rebooting:

sudo nano /etc/cron.d/temperature
/etc/cron.d/temperature

# Lance au démarrage
@reboot pi cd /home/pi/public_html/ && /opt/bin/node /home/pi/public_html/index.js >> /home/pi/log/nodejs/console.log 2>&1 &

# Test every 7 minutes and iif JSON file was not updated for more than 11 minutes, then restart Node.js
*/7 * * * * pi [ `find /home/pi/public_html/temperature.json -mmin +11` ] && touch /home/pi/public_html/cronNode.txt && (killall -q node || true) && sleep 2 && cd /home/pi/public_html/ && node /home/pi/public_html/index.js >> /home/pi/log/nodejs/console.log 2>&1 &

# Test every 13 minutes if the network connection works, otherwise reboot
*/13 * * * * root [ `ping -c3 -q  192.168.1.1 > /dev/null 2>&1` ] && touch /home/pi/public_html/cronReboot1.txt && reboot

# Test every 17 minutes and iif JSON file was not updated for more than 23 minutes, then reboot
*/17 * * * * root [ `find /home/pi/public_html/temperature.json -mmin +23` ] && touch /home/pi/public_html/cronReboot2.txt && reboot
sudo chmod +x /etc/cron.d/temperature
sudo service cron restart
Index

Conclusion

That’s all folks!
See the live demo running directly on the Raspberry Pi (when turned on) or the copy on a dedicated Web server.

Index

FAQ

Problem installing serialport?
Update to a newer version of Node.js and npm. See the current issues such as issue #81.
Index

Comments

If you expect an answer or to report a problem, favour contacting me by e-mail.

object: View comments

http://alexandre.alapetite.fr

Back