node-red-contrib-roadrunner
Version:
Node-RED nodes to control Acmesystem's Roadrunner GPIO
222 lines (192 loc) • 8.68 kB
JavaScript
module.exports = function(RED) {
"use strict";
RED.log.info("rr-gpio : initializing.");
var exec = require('child_process').execSync;
var spawn = require('child_process').spawn;
var fs = require('fs');
var gpioHelper = __dirname+'/gpiod_helper.py';
process.env.LD_LIBRARY_PATH = __dirname+'/lib/';
process.env.PYTHONPATH = __dirname+'/lib/python/';
process.env.PYTHONUNBUFFERED = 1;
var initOK = true;
try {
var cpuinfo = fs.readFileSync("/proc/cpuinfo").toString();
if (cpuinfo.indexOf(": Atmel SAMA5") === -1) {
initOK = false;
RED.log.warn("rr-gpio : "+RED._("RoadRunner CPU not detected"));
} else {
try {
fs.accessSync('/dev/gpiochip0', fs.constants.R_OK | fs.constants.W_OK)
} catch(err) {
initOK = false;
RED.log.warn("rr-gpio : "+RED._("/dev/gpiochip0 is not writable by user "+process.env.USER+" - see README for a fix"));
}
if (initOK) {
try {
exec(gpioHelper);
} catch(err) {
if (err.status == 42) {
initOK = false;
RED.log.warn("rr-gpio : "+RED._("Python3 bindings for libgpiod not found"));
}
}
}
}
} catch(err) {
initOK = false;
RED.log.warn("rr-gpio : "+RED._("RoadRunner detection failed"));
}
var pinsInUse = {};
function GPIOInNode(config) {
RED.nodes.createNode(this,config);
this.buttonState = -1;
this.pin = config.pin;
var node = this;
if (!initOK) {
node.status({fill:"grey",shape:"dot",text:"node-red:rpi-gpio.status.not-available"});
return;
}
if (!pinsInUse.hasOwnProperty(this.pin)) {
pinsInUse[this.pin] = 'in';
} else if (pinsInUse[this.pin] !== 'in') {
node.warn(RED._("node-red:rpi-gpio.errors.alreadyset",{pin:this.pin,type:pinsInUse[this.pin]}));
}
if (node.pin !== undefined) {
node.child = spawn(gpioHelper, ["in",node.pin]);
node.running = true;
node.status({fill:"green",shape:"dot",text:"node-red:common.status.ok"});
node.child.stdout.on('data', function (data) {
var d = data.toString().trim().split("\n");
for (var i = 0; i < d.length; i++) {
if (d[i] === '') { return; }
if (node.running && node.buttonState !== -1 && !isNaN(Number(d[i])) && node.buttonState !== d[i]) {
node.send({ topic:"rr/"+node.pin, payload:Number(d[i]) });
}
node.buttonState = d[i];
node.status({fill:"green",shape:"dot",text:d[i]});
if (RED.settings.verbose) { node.log("out: "+d[i]+" :"); }
}
});
node.child.stderr.on('data', function (data) {
if (RED.settings.verbose) { node.log("err: "+data+" :"); }
});
node.child.on('close', function (code) {
node.running = false;
node.child = null;
if (RED.settings.verbose) { node.log(RED._("node-red:rpi-gpio.status.closed")); }
if (node.done) {
node.status({fill:"grey",shape:"ring",text:"node-red:rpi-gpio.status.closed"});
node.done();
} else {
node.status({fill:"red",shape:"ring",text:"node-red:rpi-gpio.status.stopped"});
}
});
node.child.on('error', function (err) {
if (err.errno === "ENOENT") {
node.error(RED._("node-red:rpi-gpio.errors.commandnotfound"));
} else if (err.errno === "EACCES") {
node.error(RED._("node-red:rpi-gpio.errors.commandnotexecutable"));
} else {
node.error(RED._("node-red:rpi-gpio.errors.error",{error:err.errno}))
}
});
} else {
node.warn(RED._("node-red:rpi-gpio.errors.invalidpin")+": "+node.pin);
}
node.on("close", function(done) {
node.status({fill:"grey",shape:"ring",text:"node-red:rpi-gpio.status.closed"});
delete pinsInUse[node.pin];
if (node.child != null) {
node.done = done;
node.child.stdin.write("q\n");
node.child.kill('SIGKILL');
} else { done(); }
});
}
RED.nodes.registerType("rr-gpio in",GPIOInNode);
function GPIOOutNode(config) {
RED.nodes.createNode(this,config);
this.pin = config.pin;
this.level = config.level;
var node = this;
if (!initOK) {
node.status({fill:"grey",shape:"dot",text:"node-red:rpi-gpio.status.not-available"});
return;
}
if (!pinsInUse.hasOwnProperty(this.pin)) {
pinsInUse[this.pin] = 'out';
} else if (pinsInUse[this.pin] !== 'out') {
node.warn(RED._("node-red:rpi-gpio.errors.alreadyset",{pin:this.pin,type:pinsInUse[this.pin]}));
}
function inputlistener(msg) {
if (msg.payload === "true") { msg.payload = true; }
if (msg.payload === "false") { msg.payload = false; }
var out = Number(msg.payload);
if ((out >= 0) && (out <= 1)) {
if (RED.settings.verbose) { node.log("out: "+out); }
if (node.child !== null) {
node.child.stdin.write(out+"\n");
node.status({fill:"green",shape:"dot",text:msg.payload.toString()});
} else {
node.error(RED._("node-red:rpi-gpio.errors.pythoncommandnotfound"),msg);
node.status({fill:"red",shape:"ring",text:"node-red:rpi-gpio.status.not-running"});
}
} else {
node.warn(RED._("node-red:rpi-gpio.errors.invalidinput")+": "+out);
}
}
if (node.pin !== undefined) {
node.child = spawn(gpioHelper, ["out",node.pin, node.level]);
node.status({fill:"green",shape:"dot",text:node.level});
node.running = true;
node.on("input", inputlistener);
node.child.stdout.on('data', function (data) {
if (RED.settings.verbose) { node.log("out: "+data+" :"); }
});
node.child.stderr.on('data', function (data) {
if (RED.settings.verbose) { node.log("err: "+data+" :"); }
});
node.child.on('close', function (code) {
node.child = null;
node.running = false;
if (RED.settings.verbose) { node.log(RED._("node-red:rpi-gpio.status.closed")); }
if (node.done) {
node.status({fill:"grey",shape:"ring",text:"node-red:rpi-gpio.status.closed"});
node.done();
} else {
node.status({fill:"red",shape:"ring",text:"node-red:rpi-gpio.status.stopped"});
}
});
node.child.on('error', function (err) {
if (err.errno === "ENOENT") {
node.error(RED._("node-red:rpi-gpio.errors.commandnotfound"));
} else if (err.errno === "EACCES") {
node.error(RED._("node-red:rpi-gpio.errors.commandnotexecutable"));
} else {
node.error(RED._("node-red:rpi-gpio.errors.error",{error:err.errno}))
}
});
} else {
node.warn(RED._("node-red:rpi-gpio.errors.invalidpin")+": "+node.pin);
}
node.on("close", function(done) {
node.status({fill:"grey",shape:"ring",text:"node-red:rpi-gpio.status.closed"});
delete pinsInUse[node.pin];
if (node.child != null) {
node.done = done;
node.child.stdin.write("close "+node.pin);
node.child.kill('SIGKILL');
}
else { done(); }
});
}
RED.nodes.registerType("rr-gpio out",GPIOOutNode);
RED.httpAdmin.get('/rr-pins/:id', RED.auth.needsPermission('rr-gpio.read'), function(req,res) {
res.json(pinsInUse);
});
if (initOK) {
RED.log.info("rr-gpio : initialization complete.");
} else {
RED.log.warn("rr-gpio : "+RED._("RoadRunner specific node set inactive"));
}
}