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_v8isrmember variable. - The
uvworkclass method andthisare passed to the C functionmraa_gpio_isr. When an interrupt occursuvworkwill be called and passedthisas its sole argument.
So what does uvwork do when it gets called?
- It creates a
uv_work_tstruct and assigns thethisvalue (calledctxhere) to thedatafield of that struct. - It then calls the function
uv_queue_workwhich 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:
v8isris a class method so it has no access tothis, but it can retrieve a pointer to our Gpio instance from the passed inuv_work_tstruct - it names the pointerThis.- Via
Thisit gets at them_v8isrmember variable that wrapped our Javascript callback function. - It invokes the callback function.
- It disposes of the
reqstruct 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!