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:
- The passed in callback is wrapped up in the
m_v8isr
member variable. - The
uvwork
class method andthis
are passed to the C functionmraa_gpio_isr
. When an interrupt occursuvwork
will be called and passedthis
as its sole argument.
So what does uvwork
do when it gets called?
- It creates a
uv_work_t
struct and assigns thethis
value (calledctx
here) to thedata
field of that struct. - 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:
v8isr
is a class method so it has no access tothis
, but it can retrieve a pointer to our Gpio instance from the passed inuv_work_t
struct - it names the pointerThis
.- Via
This
it gets at them_v8isr
member variable that wrapped our Javascript callback function. - It invokes the callback function.
- It disposes of the
req
struct created byuvwork
.
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.
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!