Making the Little Lights Twinkle

My previous post, Building the RaspberryPi Christmas Light Box, explained at a hight level building out the hardware. That step was a little scary than I think it should be but it all worked out just fine in the end. Now that I had everything put together and powered on, I was stuck here:

pi@raspberrypi:~ $ 

What are the next steps? I’ve got this box all wired up and ready to go but now I’m just sitting at a prompt waiting. As I mentioned before, I broke this down into a few parts to make my life easier and not get overwhelmed. After doing some more and more reading, I figured I had two options. I could program everything in Python or I could program everything in NodeJS.

I’m comfortable either language and no matter how hard I tried I kept going in circles. Something told me that I should write it in NodeJS because I felt that I should consider a client-server model. There were TONS of examples of people that had written all kinds of programs and libraries for handling sound and music and lights and GPIOs. I ended up throwing myself a curve ball. I decided that the client-server model was indeed what I should consider for future expansion of my new found hobby of Christmas tree lighting so I decided on NodeJS.

Well Folks, Here’s the Start of the Code

It all started pretty easy. I wanted to first make sure I had all of my GPIOs hooked up correctly. I wanted to make sure things blinked on and off like I expect. It seems that the only thing I needed to make NodeJS work was the onoff package. I popped into a directory on my Pi and installed it:

npm install onoff --save

Great! I guess the next step was to steal one of the sample JS scripts that gives you an example of how to make a relay turn on and off (my blink.js is born):

var Gpio = require('onoff').Gpio; //include onoff to interact with the GPIO
var LED = new Gpio(23, 'out'); //use GPIO pin 23, and specify that it is output
var blinkInterval = setInterval(blinkLED, 250); //run the blinkLED function every 250ms

function blinkLED() { //function to start blinking
  if (LED.readSync() === 0) { //check the pin state, if the state is 0 (or off)
    LED.writeSync(1); //set pin state to 1 (turn LED on)
  } else {
    LED.writeSync(0); //set pin state to 0 (turn LED off)
  }
}

function endBlink() { //function to stop blinking
  clearInterval(blinkInterval); // Stop blink intervals
  LED.writeSync(0); // Turn LED off
  LED.unexport(); // Unexport GPIO to free resources
}

setTimeout(endBlink, 5000); //stop blinking after 5 seconds

That seemed to work just wonderfully so I wanted to make sure that I could get all of the relays to go click click for me so enter the flowled.js (This is a great way to annoy ANYONE within ear shot. Remember from my previous post that I have the analog relays so they go *click click* when they turn on and off). While my wife was excited at the thoughts of our new Christmas lighting show, she was getting annoyed of the various clicking combinations that I came up. Thank you for your patience and appearing to be just as excited as I was deer!:

var Gpio = require('onoff').Gpio; //include onoff to interact with the GPIO
var RELAY01 = new Gpio(24, 'out'), //use declare variables for all the GPIO output pins
  RELAY02 = new Gpio(25, 'out'),
  RELAY03 = new Gpio(23, 'out'),
  RELAY04 = new Gpio(22, 'out'),
  RELAY05 = new Gpio(12, 'out'),
  RELAY06 = new Gpio(13, 'out'),
  RELAY07 = new Gpio(16, 'out'),
  RELAY08 = new Gpio(26, 'out');

//Put all the RELAY variables in an array
var leds = [RELAY01, RELAY02, RELAY03, RELAY04, RELAY05, RELAY06, RELAY07, RELAY08];
var indexCount = 0; //a counter
dir = "up"; //variable for flowing direction

var flowInterval = setInterval(flowingLeds, 100); //run the flowingLeds function every 100ms

function flowingLeds() { //function for flowing Leds
  leds.forEach(function(currentValue) { //for each item in array
    currentValue.writeSync(0); //turn off RELAY
  });
  if (indexCount == 0) dir = "up"; //set flow direction to "up" if the count reaches zero
  if (indexCount >= leds.length) dir = "down"; //set flow direction to "down" if the count reaches 7
  if (dir == "down") indexCount--; //count downwards if direction is down
  leds[indexCount].writeSync(1); //turn on RELAY that where array index matches count
  if (dir == "up") indexCount++ //count upwards if direction is up
};

function unexportOnClose() { //function to run when exiting program
  clearInterval(flowInterval); //stop flow interwal
  leds.forEach(function(currentValue) { //for each RELAY
    currentValue.writeSync(0); //turn off RELAY
    currentValue.unexport(); //unexport GPIO
  });
};

process.on('SIGINT', unexportOnClose); //function to run when user closes using ctrl+c

Time to Build the REST API!

Now that I had sufficiently annoyed everyone in the house by showing them the little LED on the relays blink and click, I think it was time to actually put this to work for me. All good client server models work via APIs, right? I had dreams of multiple Pis being setup around the yard and house being controlled by a central Pi that played the music and made all of the magical lighting happen. This will indeed be the case in a few years I’m sure. But we’re crawling before we can dead sprint. With that, I added http to my project with a little:

npm install http --save

So now I had onoff and http installed and saved to my package.json. With onoff and http ready to go, it was time for me to create the REST API server. I started with a few constants:

var Gpio = require('onoff').Gpio; //include onoff to interact with the GPIO
var RELAY1 = new Gpio(24, 'out'), //use declare variables for all the GPIO output pins
  RELAY2 = new Gpio(25, 'out'),
  RELAY3 = new Gpio(23, 'out'),
  RELAY4 = new Gpio(22, 'out'),
  RELAY5 = new Gpio(12, 'out'),
  RELAY6 = new Gpio(13, 'out'),
  RELAY7 = new Gpio(16, 'out'),
  RELAY8 = new Gpio(26, 'out');

const NUM_RELAYS = 8;
const RELAYS = [RELAY1, RELAY2, RELAY3, RELAY4, RELAY5, RELAY6, RELAY7, RELAY8];

const COMMANDS = [ 'on', 'off' ];

const PORT = process.env.PORT || 8080

Of course, the first var is to bring in the GPIO control and then I mapped the various RELAY vars to the appropriate gpios that I was using on my Pi. I have a total of 8 relays to choose from and then I also created two arrays, RELAYS and COMMANDS. These will make more sense later. Finally, I’m defining a default port for my API server to run on:

var http = require('http').createServer(handler); //require http server, and create server with function handler()

console.log(`Server Running on ${PORT}`);
http.listen(PORT);

Then we fire up the http server with my “handler” middleware. The handler function is below:

function handler (req, res) { //create server
  if (req.url.startsWith('/light') && req.method == 'GET') {
    getCommands(req.url, (err, commands) => {
      if(err) {
        var message = `{"test": "Failed","message": "${err}"}`;
      } else {
        var message = doCommand(commands.relay, commands.command);
      }
      res.statusCode = 200;
      res.setHeader('Content-Type', 'application/json');
      res.end(message);
    })
  } else if ( req.url == '/status' && req.method == 'GET' ) {
    res.statusCode = 200;
    res.setHeader('Content-Type', 'application/json');
    res.end('{"status": 200, "message": "test ok"}');
  } else {
    //Set the response HTTP header with HTTP status and Content type
    res.statusCode = 200;
    res.setHeader('Content-Type', 'application/json');
    res.end('{"status": 200, "message": "ok"}');
  }
}

I setup handler to push two routes that will accept GET requests, /light and /status. The /light route is where everything happens and /status was future proofing to make sure that we could possibly check the status of the server when we build the monolithic light show like Clark W! Of course, the final “else” is my garbage eat all where I just return “ok” to any request.

For those that don’t know, I’m a security geek so I built my getCommands function into the /light route:

function getCommands(string, cb) {

  var data = string.split('/');

  if(data.length != 4) {
    return cb('Wrong Number of Arguments Provided');
  }

  if(!COMMANDS.includes(data[3])) {
    return cb('Unsupported Command');
  }

  if(data[2] > NUM_RELAYS || data[2] < 1) {
    return cb('Sorry We Cannot Control That One');
  }

  result = {
    "relay" : data[2],
    "command" : data[3]
  }

  return cb(null, result);
}

The purpose of this function is to make sure we’re not fed garbage by anyone. I’m checking to make sure we get the right number of items in the request path (aka /light/<RELAY #>/<COMMAND>). If these fail, then I fail the request and do nothing. Assuming we pass validation, we get to the work horse, doCommand:

function doCommand(relay, command) {
  var myIndex = relay - 1;
  try {
    RELAYS[myIndex].writeSync(COMMANDS.indexOf(command));
    var message = 'OMG This is Great!';
  } catch (e) {
    var message = e;
  }
  var resp = `{"status": "Ok", "message": "${message}"}`

  return resp;
}

This function just takes the received command (on or off) and runs it against the specified relay (1 – 8). In curl the command would look a little something like this to turn on relay 4:

$ curl localhost:8080/light/4/on
{"status": "Ok", "message": "OMG This is Great!"}

To turn off the same relay, we would issue:

$ curl localhost:8080/light/4/off
{"status": "Ok", "message": "OMG This is Great!"}

Now I have a NodeJS server that can handle REST API calls to be able to turn on and off certain relays. What an accomplishment! Next post will cover how I put this all together to at least do some crappy light shows.