Raspberry Pi with Node.js and Arduino

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


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


Raspberry Pi with Node.js and Arduino

http://alexandre.alapetite.fr/doc-alex/raspberrypi-nodejs-arduino/ And another one: My...
  • spaceship20008
  • spaceship20008
  • 2013年01月06日 19:54
  • 518

Arduino 与树莓派 Raspberry Pi 相比各自有什么优缺点

Arduino 与树莓派 Raspberry Pi 相比各自有什么优缺点
  • ztguang
  • ztguang
  • 2017年05月20日 16:45
  • 985

树莓派(raspberry pi)能象Arduino一样外接传感器,控制器吗?

答案是YES树莓派板子上有26只管脚,这些GPIO (general purpose I/O) 包括 SPI, I2C, 串口 UART, 3V3 and 5V 电源。国内论坛就有文章“Raspbmc...
  • berryreload
  • berryreload
  • 2013年01月19日 14:00
  • 12246

三大主流开源硬件对比:Arduino vs BeagleBone vs Raspberry Pi

摘要:本文对比了三款比较流行的,也是比较具代表意义的开源硬件平台Arduino Uno、BeagleBone和Raspberry Pi。从性能以及可扩展性等方面对它们进行对比,帮助开发者了解如何选择适...
  • arnoldlu
  • arnoldlu
  • 2013年09月25日 20:30
  • 31419

《Raspberry Pi Cookbook》pdf

内容简介 The world of Raspberry Pi is evolving quickly, with many new interface boards and software l...
  • cf406061841
  • cf406061841
  • 2017年09月16日 10:49
  • 415

树莓派(Raspberry Pi)瞎捣鼓

本周入手了新玩具Raspberry Pi 3 model B,利用周末稍微玩了一下,感觉体验还行,可以用来当个小私服来用或者用来当电视盒子娱乐用。 点赞点:支持无线网络和蓝牙哦 吐槽点:貌似没有电...
  • dushenzhi
  • dushenzhi
  • 2016年04月17日 00:07
  • 1755

Raspberry Pi 远程登录配置

1. 设置支持SSH远程登录: 使能SSH登录选项: 查看树莓派的IP地址,这里使用Wifif连接的方式,因此, IP是192.168.31.50: 2. 在电脑使用SSH登录工具登...
  • Linjingke32
  • Linjingke32
  • 2017年04月15日 13:40
  • 755

raspberry pi 的 C 程序编写

linux 都带了 gcc, 简单程序可以在树莓派中,直接编译 复杂的程序,可以用 bcm (因为芯片是 broadcom 的) 的 toolchain 来编译 http://hertavil...
  • span76
  • span76
  • 2015年04月30日 10:47
  • 1543

Raspberry Pi 3学习系列——基本使用

概述树莓派其实就是一个微型的计算机,我们可以在上面实现很多PC机的功能装各种操作系统、搭建服务器。但是不同的是在树莓派上有很多GPIO引脚,我们可以通过树莓派来控制这些引脚传输物理信号,比如让led灯...
  • Kelsey98
  • Kelsey98
  • 2018年01月02日 11:06
  • 165

Raspberry pi3 入门一

网络WiFi扫描网络iwlist wlan0 scan在如下文件/etc/wpa_supplicant/wpa_supplicant.conf 添加WIFI的ID和密码。ctrl_interface=...
  • EAIBOT
  • EAIBOT
  • 2016年04月30日 15:40
  • 3012
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Raspberry Pi with Node.js and Arduino
举报原因:
原因补充:

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