Raspberry Pi with Node.js and Arduino

转载 2013年01月07日 04:48:22


page from :http://alexandre.alapetite.fr/doc-alex/raspberrypi-nodejs-arduino/
by Alexandre Alapetite on 2012-06-23; updated on 2012-12-04

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 Samsung USB tablet charger ETA-P10X (5V, 2A) to power the Raspberry Pi, 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.

Alternatively for the USB power supply, I have also used with success a Sony Ericsson EP800 (5V, 850mA).

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.

Preparing the Raspberry Pi

There are already multiple Linux distributions for the Raspberry Pi, nicknamed RasPi. I have chosen a recent Linux Debian wheezy, 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 dpkg-reconfigure keyboard-configuration	#To change your keyboard layout if it failed during the setup

passwd	#To change your password (default is ‘raspberry’) if this was not done during the setup

su
passwd	#Change also the root password (default is ‘root’)
nano /etc/ssh/sshd_config
	PermitRootLogin no	#Change this entry to disable remote SSH root access
/etc/init.d/ssh restart
exit

sudo nano /etc/apt/sources.list	#Edit to use regional mirrors, such as http://ftp.fr.debian.org
sudo apt-get udate && sudo apt-get upgrade-dist

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

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 build-essential

However, at the time of the writing, Node.js is not yet in the stable branch of Debian.
Here is my approach to install it in this case:

sudo su
echo deb http://ftp.fr.debian.org/debian/ sid main > /etc/apt/sources.list.d/sid.list
apt-get update && apt-get install nodejs npm
rm /etc/apt/sources.list.d/sid.list
apt-get update
exit

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 you want this to run at startup (as the user “pi”), it is possible to add the command for instance to your rc.local:

sudo nano /etc/rc.local
/etc/rc.local

su pi -c 'cd /home/pi/public_html/ && node /home/pi/public_html/index.js >> /home/pi/log/nodejs/console.log 2>&1 &'

and then activate your rc.local:

sudo chmod +x /etc/rc.local

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

# 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

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.

FAQ

Problem installing serialport?
See the current issues such as issue #81.

Comments

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


Back


相关文章推荐

ROS Tutorials to Start Working with Arduino and Raspberry Pi

The robotic field is getting more and more complex, but there’s no need to worry since an army of en...
  • dxuehui
  • dxuehui
  • 2015年12月05日 13:16
  • 1283

Arduino 和 Raspberry Pi,哪个更适合创客?

原文出处:http://www.jiasu.do/p/which-better-for-maker-arduino-or-raspberry-pi/

Arduino vs Raspberry Pi vs BeagleBone

软硬件整合是今年一再被提及的话题,如今我们也可以看到不少硬件创业的成功案例,比如Jawbone Up、Pebble手表等可穿戴设备,它们可与用户的手机同步,实现软件与硬件的结合。通过硬件创新与软件整合...

Mastering Media with the Raspberry Pi epub

  • 2017年10月31日 22:36
  • 5.24MB
  • 下载

Experimenting with Raspberry Pi

  • 2015年02月24日 19:48
  • 2.95MB
  • 下载

packet_forwarder Use with Raspberry Pi

Michael Coracin edited this page on 12 Jul · 17 revisions The Semtech LoRa GW reference desig...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Raspberry Pi with Node.js and Arduino
举报原因:
原因补充:

(最多只允许输入30个字)