Skip to content

Instantly share code, notes, and snippets.

@george-hawkins
Last active July 3, 2017 00:07
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save george-hawkins/632bad44aa47d528a0eb to your computer and use it in GitHub Desktop.
Save george-hawkins/632bad44aa47d528a0eb to your computer and use it in GitHub Desktop.

Gpio interrupt callbacks in Node.js

When using the C++ Gpio method isr or the C function mraa_gpio_isr it's important to know that the passed in function will be called in the context of a low level interrupt.

As such it should be coded very carefully, and typically do as little as possible, in order not to interfere with the environment that has been interrupted.

When using the isr method in Javascript do we have to worry about this kind of thing?

The answer is no, as the logic for the Javascript wrappers ensures that the callback function passed to isr is invoked from the normal Node.js event loop.

Let's walk through what happens to confirm this.

First an example of installing a callback to be invoked when an interrupt is triggered by a pin:

var buttonPin = new mraa.Gpio(45);

buttonPin.dir(mraa.DIR_IN);

buttonPin.isr(mraa.EDGE_BOTH, function () {
    // Do something when pin state changes.
});

To uninstall this callback:

buttonPin.isrExit();

Note: you can only have one callback on a pin at a time (the reason for this is clear from the logic below).

The Javascript Gpio class is created automatically by SWIG which wraps the C++ Gpio class found in mraa/api/mraa/gpio.hpp.

Note: there's no corresponding gpio.cpp file - the Gpio methods etc., that are declared and defined in this header, call on the mraa C library to do all the real work.

If we look at Gpio class in gpio.hpp we see:

1. There's a private member variable m_v8isr.

v8::Persistent<v8::Function> m_v8isr;

2. There are a number of helper class methods - v8isr, nop and uvwork:

static void
v8isr(uv_work_t* req, int status)
{
    mraa::Gpio* This = (mraa::Gpio*) req->data;
    int argc = 1;
    v8::Local<v8::Value> argv[] = { SWIGV8_INTEGER_NEW(-1) };
    v8::Local<v8::Function> f = v8::Local<v8::Function>::New(v8::Isolate::GetCurrent(), This->m_v8isr);
    f->Call(SWIGV8_CURRENT_CONTEXT()->Global(), argc, argv);
    delete req;
}

static void
nop(uv_work_t* req)
{
    // Do nothing.
}

static void
uvwork(void* ctx)
{
    uv_work_t* req = new uv_work_t;
    req->data = ctx;
    uv_queue_work(uv_default_loop(), req, nop, v8isr);
}

Note: for ease of reading I've removed #ifdef blocks from the C++ code snippets here that are related to older versions of Node.js.

3. Then there's the actual isr instance method:

mraa_result_t
isr(Edge mode, v8::Handle<v8::Function> func)
{
    m_v8isr.Reset(v8::Isolate::GetCurrent(), func);
    return mraa_gpio_isr(m_gpio, (gpio_edge_t) mode, &uvwork, this);
}

So now we can see what happens when we call isr with a Javascript callback function:

  1. The passed in callback is wrapped up in the m_v8isr member variable.
  2. The uvwork class method and this are passed to the C function mraa_gpio_isr. When an interrupt occurs uvwork will be called and passed this as its sole argument.

So what does uvwork do when it gets called?

  1. It creates a uv_work_t struct and assigns the this value (called ctx here) to the data field of that struct.
  2. It then calls the function uv_queue_work which is going to get us back into the Node.js world.

In uvwork we're still in the low level interrupt handling world and should try to do as little as possible here.

libuv is the heart of the asynchronous Node.js world, uv_queue_work and uv_default_loop are functions provided by libuv.

So uv_queue_work is called like this:

uv_queue_work(uv_default_loop(), req, nop, v8isr);

This sets up a task (here nop) to be run on a separate thread and a callback (here v8isr) that is to be called from a particular event loop (here uv_default_loop) when the task is complete.

Both the task and the callback will be passed the req value, i.e. the uv_work_t struct that was created.

In our case we're not interested in the task, so nop does nothing, instead we're interested in getting v8isr called from the uv_default_loop which is the standard Node.js event loop.

So what happens when v8isr does get called:

  1. v8isr is a class method so it has no access to this, but it can retrieve a pointer to our Gpio instance from the passed in uv_work_t struct - it names the pointer This.
  2. Via This it gets at the m_v8isr member variable that wrapped our Javascript callback function.
  3. It invokes the callback function.
  4. It disposes of the req struct created by uvwork.

So in the end our Javascript callback function gets called in the nice safe environment of v8isr and the normal Node.js event loop rather than the interrupt environment that uvwork runs in.

@Hmbucker
Copy link

Hi, I am trying to make a small application that basically calls a script to play a wav file whenever a sensor is triggered on a Gpio. The hardware is working (playing of wav works like a charm) but I am pulling my hear out due to following error whenever I try to rewrite my script from polling to an interrupt service routine:

sensorPin.isr(mraa.EDGE_FALLING, function () {
^
Error: Illegal number of arguments for _wrap_Gpio_isr.
at Object. (/home/root/start.js:19:11)
at Module._compile (module.js:456:26)
at Object.Module._extensions..js (module.js:474:10)
at Module.load (module.js:356:32)
at Function.Module._load (module.js:312:12)
at Function.Module.runMain (module.js:497:10)
at startup (node.js:119:16)
at node.js:906:3

Below is the script I have created:

var mraa = require('mraa'); //require mraa
console.log('MRAA Version: ' + mraa.getVersion()); //write the mraa version to the Intel XDK console

var spawn = require('child_process').spawn;
var interruptTriggered = false;

var deploySh = function(){
var spawnedProcess = spawn('sh', [ 'birdOfPrey.sh' ]);
spawnedProcess.on('close', function(code){
console.log('child process exited with code ' + code);
interruptTriggered = false;
});
};

//initialising the pin stuff:
var sensorPin = new mraa.Gpio(15); //Movement sensor hooked up on pin 18/2 and 1.8V on 19/2
sensorPin.dir(mraa.DIR_IN); //set gpio to input
sensorPin.isr(mraa.EDGE_FALLING, function () {
if (interruptTriggered)
{
console.log('skipping this one...');
}
else
{
interruptTriggered = true;
fakeBirdOfPrey();
}
});

function fakeBirdOfPrey(count)
{
'use strict';
console.log('Argh argh, get lost stupid bird!!!');
deploySh(); //will call a shell script starting the .wav to play
}

Any help in this would be much appreciated!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment