Events & Callbacks

If you are used to programming hardware in an Arduino environment, this section might seem a bit foreign to you. Unless you are using interrupts, code execution happens in logical order. When dealing with events & callbacks, specific chunks of code gets called as events happen. If you are used to developing with Javascript, then you should feel right at home.

Callbacks

We've already discussed events earlier in the guide, but what's a callback? A callback is a function that is passed as a parameter to another function so it can be used at any point in the future.

What on earth does this have to do with events & event listeners? When dealing with asynchronous events, your callback function defines the actions that happen in response to events.  If you want a LED to light up when a button press happens, then you would write the code that turns on the LED inside of a callback. Let's take a look at an example using the Adafruit T-Cobbler to connect the button and LED.

We will show you how to download and run the example code in the last section of the guide, but if you would like to follow along and create the example files yourself, you will need to install the onoff npm package.

npm install onoff

Next, you can use nano text editor to create the test file. If you need help getting started with nano, check out this handy guide.

nano test.js

Next, paste the following example into nano, and save the file.

// button is attached to pin 17, led to 18
var GPIO = require('onoff').Gpio,
    led = new GPIO(18, 'out'),
    button = new GPIO(17, 'in', 'both');

// define the callback function
function light(err, state) {
  
  // check the state of the button
  // 1 == pressed, 0 == not pressed
  if(state == 1) {
    // turn LED on
    led.writeSync(1);
  } else {
    // turn LED off
    led.writeSync(0);
  }
  
}

// pass the callback function to the
// as the first argument to watch()
button.watch(light);

From there, you can run the example file using the node command.  Go ahead run the test file and press the button to make sure that your LED lights up.

node test.js

Let's break down what is happening in the example code. button.watch(callback) is a function that you can call if you would like to watch a button for changes in state. When a change event happens, the watch() function calls the callback function that was passed to it as its only parameter. In this case, the watch function will call the light() function when the button changes state.  

When watch() notices that the button state changed, the light() function is called with two parameters.  The first parameter has any errors that may have occurred, and the second parameter gives the current button state. When I wrote the function definition for the light method, I called the first parameter err, and the second parameter state, but you could name them whatever you would like. It's best to name them after what they represent if possible. 

How did I know that the callback would be called with those two parameters?  The documentation for the onoff GPIO library's watch() function demonstrates that it will call the callback with errors as the first parameter and the button value/state as the second.

Callbacks don't have to be defined separately, and then passed to the functions that will call them. You can define the callback and pass it to the watch function all in the same step. The only drawback to this method is that your callback can't be used by any other buttons.  Here's an example of defining your callback & passing it to watch() in the same step.

// button is attached to pin 17, LED to 18
var GPIO = require('onoff').Gpio,
    led = new GPIO(18, 'out'),
    button = new GPIO(17, 'in', 'both');

// pass the callback function to the
// as the first argument to watch() and define
// it all in one step
button.watch(function(err, state) {
  
  // check the state of the button
  // 1 == pressed, 0 == not pressed
  if(state == 1) {
    // turn LED on
    led.writeSync(1);
  } else {
    // turn LED off
    led.writeSync(0);
  }
  
});

Now that you have a handle on events & callbacks, let's compare how you would approach things in node.js vs how you would approach things in Arduino.

Arduino & node.js Examples

Let's take a look at how you would approach a couple simple tasks in Arduino, and then compare that with how you would tackle the same tasks using node.js.

Let's start with a classic example. Blinking a LED! First up, Arduino.

// LED pin
int led = 13;

void setup() {                
  // initialize the LED pin as an output
  pinMode(led, OUTPUT);     
}

void loop() {
  // turn the LED on
  digitalWrite(led, HIGH);
  // delay for one second
  delay(1000);
  // turn the LED off  
  digitalWrite(led, LOW);
  // delay for one second  
  delay(1000);
}

What's going on here? As the sketch runs the main loop, it flips the LED on and off separated by delays of one second. Now, let's look at the same example using node.js on a Raspberry PI.

// export GPIO 18 as an output.
var GPIO = require('onoff').Gpio,
    led = new GPIO(18, 'out');

// start a timer that runs the callback
// function every second (1000 ms)
setInterval(function() {  
  // get the current state of the LED
  var state = led.readSync();
  // write the opposite of the current state to the LED pin
  led.writeSync(Number(!state));
}, 1000);

What's going on here? The same thing, but the difference is that the Arduino delay function blocks all other code from executing for one second for each call to delay(1000), and the node.js setInterval timer does not.

Why does this matter? Let's say you wanted to blink a green LED on and off every second, and you wanted to control the state of a red LED with a momentary button. Let's take a look at another Arduino example sketch.

const int buttonPin = 2;
const int redPin =  10;
const int greenPin = 11;

void setup() {
  // initialize the LED pins as outputs
  pinMode(redPin, OUTPUT);
  pinMode(greenPin, OUTPUT);      
  // initialize the button pin as an input:
  pinMode(buttonPin, INPUT);
}

void loop() {
  
  // read the state of the pushbutton value
  int buttonState = digitalRead(buttonPin);
  // turn the red LED on or off depending on
  // the button state.
  digitalWrite(redPin, buttonState);
  
  // turn the green LED on
  digitalWrite(greenPin, HIGH);
  // delay for one second
  delay(1000);
  // turn the green LED off
  digitalWrite(greenPin, LOW);
  // delay for one second
  delay(1000);
  
}

Since we are using Arduino's delay function, the button presses could be missed because the loop has to make it through both calls to delay(1000) before checking the button state.

Note: I know some of the more experienced Arduino users can think of a ways to avoid using delay(), and solve this problem with interrupts or by checking the current millis() against previous millis() + 1000. This is just a simple example designed to highlight how things can easily get a lot more complicated when you don't have a way to call code asynchronously.

Now let's look at the same thing on a Raspberry Pi using node.js.

// export GPIO 17 as the red LED output, GPIO 18 as
// the button input, and GPIO green as the button input.
var GPIO = require('onoff').Gpio,
    green = new GPIO(21, 'out'),
    red = new GPIO(17, 'out'),
    button = new GPIO(18, 'in', 'both');

// watch the button for changes, and pass
// the button state (1 or 0) to the red LED
button.watch(function(err, state) {
  red.writeSync(state);
});

// start a timer that runs the callback every second
setInterval(function() {
  // get the current state of the LED
  var state = green.readSync();
  // write the opposite of the current
  // green LED state to the green LED pin
  green.writeSync(Number(!state));
}, 1000);

Next, let's look at how to make things even simpler with node.js streams!

Last updated on 2017-03-04 at 03.40.08 PM Published on 2014-12-19 at 04.57.54 PM