Comandare un LED con il Raspberry attraverso un web server REST in Node.js

Avete la necessità di accendere un led o in generale leggere/impostare un qualsiasi pin del GPIO del Raspberry Pi via web? La soluzione più semplice e comoda è probabilmente utilizzare un server REST in Javascript con Node.js.

Esistono sicuramente anche altre valide alternative, io ho scelto questa perché negli ultimi mesi mi sono trovato diverse volte a lavorare con Node.Js e l’ho apprezzato veramente tanto per la sua semplicità e al contempo grossa potenza modulare, con un’infinità di pacchetti ed estensioni NPM.

Lo scopo di questa guida è quindi quello di accendere un led attaccato al gpio (con una resistenza e un mosfet, che non sono oggetto di questo post ma di cui potete trovare già tantissima documentazione) attraverso una semplice chiamata web http://indirizzo.ip/led/on.

Due led incastonati nel case del Raspberry, comandati da NodeJs

Due led incastonati nel case del Raspberry, comandati da NodeJs

Parto dal presupposto che il Raspberry sia già stato configurato con la Raspbian installata ed attiva e che quindi abbiate già una conoscenza base generale del sistema.

Installare i pacchetti necessari con

curl -sLS https://apt.adafruit.com/add | sudo bash

sudo apt-get install node

Verificate che node sia installato correttamente:

pi@raspberrypi node -v
v0.12.0

creiamo un nuovo progetto ed installiamo le librerie necessarie

mkdir /home/pi/raspiledjs
cd /home/pi/raspiledjs

npm init

E rispondere alle domande per creare il package del progetto

Adesso possiamo installare le dipendenze necessarie: Express (per creare il server REST), rpi-gpio (per gestire i GPIO del Rpi)

npm install express rpi-gpio --save

Io ho inoltre voluto creare una pagina html semplice con dei bottoni per accendere e spegnere il led. Anche questa pagina sarà servita da node che funzionerà da web server.

Per creare l’interfaccia velocemente ho utilizzato boostrap e jquery, anch’esse installate con NPM:

npm install bootstrap jquery cors --save

Adesso che abbiamo installato il necessario, passiamo al codice dell’applicazione. Io ho creato un “index.js” con questo contenuto:

var http = require('http');
var express = require('express');
var gpio = require('rpi-gpio');
var exec = require('child_process').exec;
var cors = require('cors');
var app = express();
app.use(express.static(__dirname + '/public'));
app.use('/node_modules/bootstrap', express.static(__dirname + '/node_modules/bootstrap'));
app.use('/node_modules/jquery', express.static(__dirname + '/node_modules/jquery'));
app.use(cors());

var LEDPIN = 8;
var SERVERPORT = 80; 

app.get('/led/:on', function (req, res) {
  console.log('led request = ' + req.params.on);
  if (req.params.on == 'on') {
    setLed(LEDPIN,1, function(err){
        if (err) {
            res.status(500).send('Oops, Something went wrong! ' + err);
        } else {
            res.status(200).send("LED 1 ON");
        }
    });
    
  } else if (req.params.on == 'off') {
    setLed(LEDPIN,0, function(err){
        if (err) {
            res.status(500).send('Oops, Something went wrong! ' + err);
        } else {
            res.status(200).send("LED 1 OFF");
        }
    });
  } 
  else if (req.params.on == 'read') {
    readStatus(LEDPIN, function(err, value){
        if (err) {
            res.status(500).send('Oops, Something went wrong! ' + err);
        } else {
            res.status(200).send(value);
        }
    });
  } else {
    res.status(404).send("Bad Request. Use /led1/on or /led1/off or /led1/read");
  }
}); 

app.post('/shutdown', function (req, res) {
  res.status(200).send("Raspberry is shutting down...");
  unexportPins();
  execute("halt");
});

app.post('/restart', function (req, res) {
  res.status(200).send("Raspberry is restarting...");
  unexportPins();
  execute("restart");
});

// Express route for any other unrecognised incoming requests
app.get('*', function (req, res) {
  res.status(404).send('Unrecognised API call.');
});

// Express route to handle errors
app.use(function (err, req, res, next) {
  if (req.xhr) {
    res.status(500).send('Oops, Something went wrong!');
  } else {
    next(err);
  }
}); 

function setLed(PIN, value, callback) {
    console.log("Setting pin "+PIN+" to " + value);
    gpio.write(PIN, value, function(err) {
        if (err) {
                console.log("error writing " + err);
                callback("error writing " + err);
                return;
            }
            callback();
    });
}

function readStatus(PIN, callback) {
    console.log("reading pin "+PIN);
    gpio.read(PIN, function(err,value) {
        if (err) {
                console.log("error reading pin " + err, null);
                callback("error reading pin " + err, null);
                return;
            }
        callback(null,value);
    });
}

//catches ctrl+c event
process.on('SIGINT', function(){
     unexportPins();
});

function unexportPins() {
  gpio.destroy(function() {
    console.log('All pins unexported');
    process.exit();
  });  
}

function execute(command) {
  exec(command, function (error, stdout, stderr) {
  console.log('exec: ' + stdout);
  console.log('stderr: ' + stderr);
  if (error !== null) {
    console.log('exec error: ' + error);
  }
});
}

//init pin and app 
gpio.setup(LEDPIN, gpio.DIR_OUT, function(err){
  if (err) {
    console.log("Error opening pin " + err);
    return;
  }
  app.listen(SERVERPORT);
  console.log('GPIO setup completed and server listening on port ' + SERVERPORT);
});

L’unica configurazione da fare sono le variabili LEDPIN e SERVERPORT che trovate alle righe 12 e 13 dello script:

  • LEDPIN è il numero del GPIO che volete pilotare (segue la numerazione standard del Raspberry Pi, con il pin 1 in alto a sinistra, il secondo in alto a destra e così via. Riferimento completo qui.)
  • SERVERPORT è la porta sulla quale starà in ascolto il server. La porta 80 permette di accedere agli indirizzi semplicemente con http://ipraspberry/led/on

Una volta salvato il file, possiamo testarlo con un’unica accortezza: eseguirlo con i permessi di root, altrimenti il raspberry non riuscirà a gestire i GPIO

sudo node index.js

se tutto è andato bene dovreste leggere una frase del tipo

GPIO setup completed and server listening on port 80

a questo punto potete provare ad attivare il vostro GPIO semplicemente da un qualsiasi browser in rete con il raspberry (anche da smartphone), aprendo l’indirizzo

http://192.168.1.10/led/on

dove 192.168.1.10 è l’ip del Raspberry in questo caso. (vi consiglio di impostarlo statico…)

Nella consolle di Node dovreste leggere contemporaneamente

 led request = on Setting pin 8 to

Gli endpoint che questo script rende disponibili sono i seguenti:

  • [GET] http://192.168.1.10/led/on – Attiva il pin
  • [GET] http://192.168.1.10/led/off – Disattiva il pin
  • [GET] http://192.168.1.10/led/read – Legge lo stato del pin
  • [POST] http://192.168.1.10/shutdown – Spegne il Raspberry
  • [POST] http://192.168.1.10/reboot – Riavvia il Raspberry

Ovviamente al pin comandato potrete collegare qualsiasi cosa, non solo un led !

Se infine volete eseguire uno script node in automatico all’avvio, vi consiglio l’ottimo Forever.

Interfaccia web

Lascio qua anche la paginetta web che ho creato per testare i servizi. Sia il file index.html che functions.js vanno salvati nella cartella “public” dell’applicazione node appena creata (ovvero /home/pi/raspiledjs/public ) e saranno raggiungibili semplicemente all’indirizzo http://ipdelraspberry/
nota: all’inizio del file functions.js inserire l’ip del raspberry nella variabile url.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>RASPiLedJs</title>
    <!-- Bootstrap -->
    <link href="../node_modules/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet">
    <script type="text/javascript" src="./functions.js"></script>
  </head>
  <body>

<h1>RASPiLedJs</h1>

    <label>Last response: </label>
    <input type="text" id="response">

<h2>LED</h2>


<div class="btn-group" role="group" aria-label="...">
      <button type="button" class="btn btn-default" onclick="setLed('on')">LED ON</button>
      <button type="button" class="btn btn-default" onclick="setLed('off')">LED OFF</button>
    </div>


<h2>SYSTEM</h2>


<div class="btn-group" role="group" aria-label="...">
      <button type="button" class="btn btn-default" onclick="restart()">RESTART</button>
      <button type="button" class="btn btn-default" onclick="shutdown()">SHUTDOWN</button>
    </div>

    <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
    <script src="../node_modules/jquery/dist/jquery.min.js"></script>
    <!-- Include all compiled plugins (below), or include individual files as needed -->
    <script src="../node_modules/bootstrap/dist/js/bootstrap.min.js"></script>
  </body>
</html>
var url = "http://192.168.1.125/";

function setLed (status) {
    var xmlHttp = new XMLHttpRequest();
    xmlHttp.open( "GET", url+"led/"+status , true );
    xmlHttp.onreadystatechange = function() {
      if (xmlHttp.readyState === 4)  { 
        var serverResponse = xmlHttp.responseText;
        output(serverResponse);
      }
    };
    xmlHttp.send( null );
}

function shutdown () {
    var xmlHttp = new XMLHttpRequest();
    xmlHttp.open( "POST", url+"shutdown" , true );
    xmlHttp.onreadystatechange = function() {
      if (xmlHttp.readyState === 4)  { 
        var serverResponse = xmlHttp.responseText;
        output(serverResponse);
      }
    };
    xmlHttp.send( null );
}

function restart () {
    var xmlHttp = new XMLHttpRequest();
    xmlHttp.open( "POST", url+"restart" , true ); 
    xmlHttp.onreadystatechange = function() {
      if (xmlHttp.readyState === 4)  { 
        var serverResponse = xmlHttp.responseText;
        output(serverResponse);
      }
    };
    xmlHttp.send( null );
}

function output(text) {
        var elem = document.getElementById("response");
        if (elem!= null)
          elem.value = text;
}