My typical Raspberry Pi projects are done in Python. I thought that I’d try some Node.js testing because I find the standalone Python Webserver (http.server library) to be a little slow on the Pi hardware.
Getting Started with Node.js on Raspberry Pi
Node.js can be installed on your Pi by:
$ sudo apt-get update $ sudo apt-get install -y nodejs
There are a few options on how to talk to the GPIO (General Purpose Input/Output) pins on the Pi. I tested a few and I found that pigpio worked well for my setup. It is installed by:
sudo apt-get install pigpio
To set a GPIO pin to be an output and next to turn it on, a simple Node.js program (gpio.js) would be:
// gpio.js - set GPIO pin 4 to ON const Gpio = require('pigpio').Gpio; const led = new Gpio(4, {mode: Gpio.OUTPUT}); led.digitalWrite(1);
To run the program:
sudo node gpio.js
It is important to note that access to the GPIO pins require admin rights so you will need to run your scripts with sudo (super user do).
Node.js Webserver
To make a simple webserver (mywebserver.js) :
// mywebserver.js - a simple websever on port 8080
//
var http = require('http').createServer(handler); //require http server, and create server with function handler()
var fs = require('fs'); //require filesystem module
http.listen(8080); //listen to port 8080
function handler (req, res) { //create server
fs.readFile(__dirname + '/index.html', function(err, data) { //read file index.html in public folder
if (err) {
res.writeHead(404, {'Content-Type': 'text/html'}); //display 404 on error
return res.end("404 Not Found");
}
res.writeHead(200, {'Content-Type': 'text/html'}); //write HTML
res.write(data); //write data from index.html
return res.end();
});
}
This script references the http and the fs (file system) modules, and this allow us to reference an external index.html page. The handler function is used to manage the HTTP requests.
Next you’ll need to make an index.html page, below is a simple example:
<!DOCTYPE html> <html> <body> <h1>Dummy HTML Test Page</h1><hr> </html> </body> </html>
To test this page run: node mywebserver.js , and from a browser use your Pi’s IP address with port 8080, for example :
Make the Web Page Dynamic
To make the Web Page dynamic we can use the socket.io package. It is installed by:
$ npm install socket.io --save
By using socket.io, javascript functions on the web page can communicate to functions running on the Node.js web server.
Once a function is created in the Node.js webserver application the Web page can pass data to that function.
The Raspberry Pi Rover
There are some low cost Arduino car frames that cost under $10. These car frames can be used with a Raspberry Pi but you will need to be careful on how the motors are powered. Depending on the motors you might be able to drive them directly from Pi GPIO pins, however it is recommended that you use some external hardware to protect your Pi. There are some good Pi motor top options available, for my project I used the Pimoroni Explorer Hat Pro.
My hardware setup used a Pi 3 with a portable phone charger. I used some duct tape to secure the wiring, charger and Pi together.
The motor pins will vary based on hardware that you use, so my code my need to be tweeked for your setup. The ‘control’ function is what I used to define the motor state. Some key words : forward, left, right, stop or back were passed between the web page and the server app to define the rover’s motor action.
// ws_2_rover.js - NodeJS WebServer App to control a Rover var http = require('http').createServer(handler); //require http server, and create server with function handler() var fs = require('fs'); //require filesystem module var io = require('socket.io')(http) //require socket.io module and pass the http object (server) const Gpio = require('pigpio').Gpio; // modify for your motor pinouts const MOTOR1 = new Gpio(21, {mode: Gpio.OUTPUT}); const MOTOR2 = new Gpio(19, {mode: Gpio.OUTPUT}); const MOTOR3 = new Gpio(20, {mode: Gpio.OUTPUT}); const MOTOR4 = new Gpio(26, {mode: Gpio.OUTPUT}); // Ensure that the rover app starts without the motors running function rover_stop() { MOTOR1.digitalWrite(0); MOTOR2.digitalWrite(0); MOTOR3.digitalWrite(0); MOTOR4.digitalWrite(0); } http.listen(8080); //listen to port 8080 function handler (req, res) { //create server fs.readFile(__dirname + '/web_2_rover.htm', function(err, data) { //read file index.html in public folder if (err) { res.writeHead(404, {'Content-Type': 'text/html'}); //display 404 on error return res.end("404 Not Found"); } res.writeHead(200, {'Content-Type': 'text/html'}); //write HTML res.write(data); //write data from index.html return res.end(); }); } rover_stop(); io.sockets.on('connection', function (socket) {// WebSocket Connection console.log('A user connected'); var controlvalue = ""; // variable for current status of the rover socket.on('control', function(data) { //get light switch status from client controlvalue = data; console.log('control input: ' + data); rover_stop(); // stop the motors, and then do the required action if (controlvalue == "forward") { MOTOR1.digitalWrite(1); MOTOR2.digitalWrite(1); } if (controlvalue == "left") { MOTOR2.digitalWrite(1); } if (controlvalue == "right") { MOTOR1.digitalWrite(1); } if (controlvalue == "backward") { MOTOR3.digitalWrite(1); MOTOR4.digitalWrite(1); } }); });
I used the Bootstrap template to offer a mobile friendly web interface. A button onclick function was used to pass the requested motor action (forward, left, right, stop, backward) to the control socket function. Below is my web page (web_2_rover.htm):
<!DOCTYPE html> <html> <head> <title>NodeJS Web Rover Control</title> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css"> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.0.3/socket.io.js"></script> <script> var socket = io(); //load socket.io-client and connect to the host that serves the page </script> </head> <body> <div class="container"> <h1>NodeJS Web Rover Control</h1> <button onclick="socket.emit('control','forward')" class="btn btn-success" style="width: 100%">Forwards</button> <button onclick="socket.emit('control','left')" class="btn btn-primary" style="width: 49%">Left</button> <button onclick="socket.emit('control','right')" class="btn btn-primary" style="width: 49%">Right</button> <button onclick="socket.emit('control','stop')" class="btn btn-danger" style="width: 100%">Stop</button> <button onclick="socket.emit('control','backward')" class="btn btn-warning" style="width: 100%">Backwards</button> </div> </body> </html>
To run the rover app enter:
sudo node ws_2_rover.js
Final Comments
I have done this project also in Python. The Python code is a little cleaner because the Pimoroni Explorer Hat has a Python library so I could easily adjust the motor speeds. However I found that the Node.js web interface to be a little faster than Python on the Pi.
hi there! this looks like exactly what I’m trying to do… I notice you have a stop button… so i assume buttons are not momentary (hold down for forward let go it stops) i assume its press forward and forward is latched on until you hit stop? I require momentary, hold forward-goes forward until i let go.. Can this be done and any code examples? Thanks!!
LikeLike
Hi,
I might try using the mouseup/mousedown action instead of the click action. So on the button object:
onmousedown=”socket.emit(‘control’,’forward’)” onmouseup=socket.emit(‘control’,’stop’)
I hope that this works for you.
Pete
LikeLike
Hi,
I might try using the mouseup/mousedown action instead of the click action. So on the button object:
onmousedown=”socket.emit(‘control’,’forward’)” onmouseup=socket.emit(‘control’,’stop’)
I hope that this works for you.
Pete
LikeLike