1
0
mirror of https://github.com/node-red/node-red.git synced 2023-10-10 13:36:53 +02:00

New Pi GPIO node based on RPI.GPIO library.

Adds PWM support of outputs and easier access to interrupts for inputs.
This commit is contained in:
dceejay 2014-12-27 13:11:44 +00:00
parent b4dc66944a
commit 9e4187d6a8
4 changed files with 327 additions and 132 deletions

View File

@ -45,7 +45,6 @@
<option value="tri">none</option>
<option value="up">pullup</option>
<option value="down">pulldown</option>
<!--<option value="tri">tristate</option>-->
</select>
</div>
<div class="form-row">
@ -63,11 +62,11 @@
<script type="text/x-red" data-help-name="rpi-gpio in">
<p>Raspberry Pi input node. Generates a <b>msg.payload</b> with either a 0 or 1 depending on the state of the input pin. Requires the gpio command to work.</p>
<p>You may also enable the input pullup resitor or the pulldown resistor.</p>
<p>You may also enable the input pullup resistor or the pulldown resistor.</p>
<p>The <b>msg.topic</b> is set to <i>pi/{the pin number}</i></p>
<p>Requires the RPi.GPIO python library version 0.5.8 (or better) in order to work.</p>
<p><b>Note:</b> we are using the actual physical pin numbers on connector P1 as they are easier to locate.</p>
<p><b>Note:</b> This node currently polls the pin every 250mS. This is not ideal as it loads the cpu.</p>
</script>
</script>
<script type="text/javascript">
RED.nodes.registerType('rpi-gpio in',{
@ -90,6 +89,7 @@
},
oneditprepare: function() {
var pinnow = this.pin;
var pinsInUse = {};
$.getJSON('rpi-gpio/'+this.id,function(data) {
$('#pitype').text(data.type);
if ((data.type === "Model B+") || (data.type === "Model A+")) {
@ -107,11 +107,31 @@
$('#node-input-pin').val(pinnow);
}
});
$.getJSON('rpi-pins/'+this.id,function(data) {
pinsInUse = data || {};
});
$("#node-input-pin").change(function() {
var pinnew = $("#node-input-pin").val();
if ((pinnew) && (pinnew !== pinnow)) {
if (pinsInUse.hasOwnProperty(pinnew)) {
RED.notify("Pin "+pinnew+" already in use.","info");
}
pinnow = pinnew;
}
});
$("#node-input-intype").change(function() {
var newtype = $("#node-input-intype option:selected").val();
if ((pinsInUse.hasOwnProperty(pinnow)) && (pinsInUse[pinnow] !== newtype)) {
RED.notify("Pin "+pinnow+" already set as "+pinsInUse[pinnow],"error");
}
});
}
});
</script>
<script type="text/x-red" data-template-name="rpi-gpio out">
<div class="form-row">
<label for="node-input-pin"><i class="fa fa-circle"></i> GPIO Pin</label>
@ -123,7 +143,7 @@
<option value="8">8 - TxD </option>
<option value="10">10 - RxD </option>
<option value="11">11 - GPIO0</option>
<option value="12">12 - GPIO1 (PWM)</option>
<option value="12">12 - GPIO1</option>
<option value="13">13 - GPIO2</option>
<option value="15">15 - GPIO3</option>
<option value="16">16 - GPIO4</option>
@ -169,7 +189,8 @@
<p>Raspberry Pi output node. Expects a <b>msg.payload</b> with either a 0 or 1 (or true or false). Requires the gpio command to work.</p>
<p>Will set the selected physical pin high or low depending on the value passed in.</p>
<p>The initial value of the pin at deploy time can also be set to 0 or 1.</p>
<p>Use of PWM on Pin 12 - GPIO1 will interfere with any other audio playback.</p>
<p>When using PWM mode - expects an input value of a number 0 - 100.</p>
<p>Requires the RPi.GPIO python library version 0.5.8 (or better) in order to work.</p>
<p><b>Note:</b> we are using the actual physical pin numbers on connector P1 as they are easier to locate.</p>
</script>
@ -189,7 +210,7 @@
icon: "rpi.png",
align: "right",
label: function() {
if ((this.pin == 12) && (this.out === "pwm")) { return this.name || "PWM: 12"; }
if (this.out === "pwm") { return this.name || "PWM: "+this.pin; }
else { return this.name||"Pin: "+this.pin ; }
},
labelStyle: function() {
@ -197,6 +218,7 @@
},
oneditprepare: function() {
var pinnow = this.pin;
var pinsInUse = {};
if (!$("#node-input-out").val()) { $("#node-input-out").val("out"); }
$.getJSON('rpi-gpio/'+this.id,function(data) {
$('#pitype').text(data.type);
@ -216,17 +238,26 @@
}
});
var hidepwm = function() {
if ($('#node-input-pin').val() == 12) {
$('#node-set-pwm').show();
$.getJSON('rpi-pins/'+this.id,function(data) {
pinsInUse = data || {};
});
$("#node-input-pin").change(function() {
var pinnew = $("#node-input-pin").val();
if ((pinnew) && (pinnew !== pinnow)) {
if (pinsInUse.hasOwnProperty(pinnew)) {
RED.notify("Pin "+pinnew+" already in use.","info");
}
else {
$('#node-set-pwm').hide();
$('#node-input-out').val("out");
pinnow = pinnew;
}
};
$("#node-input-pin").change(function () { hidepwm(); });
hidepwm();
});
$("#node-input-out").change(function() {
var newtype = $("#node-input-out option:selected").val();
if ((pinsInUse.hasOwnProperty(pinnow)) && (pinsInUse[pinnow] !== newtype)) {
RED.notify("Pin "+pinnow+" already set as "+pinsInUse[pinnow],"error");
}
});
var hidestate = function () {
if ($("#node-input-out").val() === "pwm") {

View File

@ -18,179 +18,202 @@ module.exports = function(RED) {
"use strict";
var util = require("util");
var exec = require('child_process').exec;
var spawn = require('child_process').spawn;
var fs = require('fs');
var gpioCommand = '/usr/local/bin/gpio';
var gpioCommand = __dirname+'/nrgpio';
if (!fs.existsSync("/dev/ttyAMA0")) { // unlikely if not on a Pi
//util.log("Info : Ignoring Raspberry Pi specific node.");
throw "Info : Ignoring Raspberry Pi specific node.";
}
if (!fs.existsSync(gpioCommand)) { // gpio command not installed
throw "Info : Can't find Raspberry Pi wiringPi gpio command.";
if (!fs.existsSync("/usr/share/doc/python-rpi.gpio")) {
util.log("[rpi-gpio] Info : Can't find Pi RPi.GPIO python library.");
throw "Warning : Can't find Pi RPi.GPIO python library.";
}
// Map physical P1 pins to Gordon's Wiring-Pi Pins (as they should be V1/V2 tolerant)
var pintable = {
// Physical : WiringPi
"11":"0",
"12":"1",
"13":"2",
"15":"3",
"16":"4",
"18":"5",
"22":"6",
"7":"7",
"3":"8",
"5":"9",
"24":"10",
"26":"11",
"19":"12",
"21":"13",
"23":"14",
"8":"15",
"10":"16",
"27":"30",
"28":"31",
"29":"21",
"31":"22",
"32":"26",
"33":"23",
"35":"24",
"36":"27",
"37":"25",
"38":"28",
"40":"29"
}
var tablepin = {
// WiringPi : Physical
"0":"11",
"1":"12",
"2":"13",
"3":"15",
"4":"16",
"5":"18",
"6":"22",
"7":"7",
"8":"3",
"9":"5",
"10":"24",
"11":"26",
"12":"19",
"13":"21",
"14":"23",
"15":"8",
"16":"10",
"30":"27",
"31":"28",
"21":"29",
"22":"31",
"26":"32",
"23":"33",
"24":"35",
"27":"36",
"25":"37",
"28":"38",
"29":"40"
if ( !(1 & parseInt ((fs.statSync(gpioCommand).mode & parseInt ("777", 8)).toString (8)[0]) )) {
util.log("[rpi-gpio] Error : "+gpioCommand+" needs to be executable.");
throw "Error : nrgpio must to be executable.";
}
var pinsInUse = {};
var pinTypes = {"out":"digital output", "tri":"input", "up":"input with pull up", "down":"input with pull down", "pwm":"PWM output"};
function GPIOInNode(n) {
RED.nodes.createNode(this,n);
this.buttonState = -1;
this.pin = pintable[n.pin];
this.pin = n.pin;
this.intype = n.intype;
this.read = n.read || false;
if (this.read) { this.buttonState = -2; }
var node = this;
if (!pinsInUse.hasOwnProperty(this.pin)) {
pinsInUse[this.pin] = this.intype;
}
else {
if ((pinsInUse[this.pin] !== this.intype)||(pinsInUse[this.pin] === "pwm")) {
node.error("GPIO pin "+this.pin+" already set as "+pinTypes[pinsInUse[this.pin]]);
}
}
if (node.pin !== undefined) {
exec(gpioCommand+" mode "+node.pin+" "+node.intype, function(err,stdout,stderr) {
if (err) { node.error(err); }
else {
node._interval = setInterval( function() {
exec(gpioCommand+" read "+node.pin, function(err,stdout,stderr) {
if (err) { node.error(err); }
else {
if (node.buttonState !== Number(stdout)) {
var previousState = node.buttonState;
node.buttonState = Number(stdout);
if (previousState !== -1) {
var msg = {topic:"pi/"+tablepin[node.pin], payload:node.buttonState};
node.send(msg);
if (node.intype === "tri") {
node.child = spawn(gpioCommand, ["in",node.pin]);
} else {
node.child = spawn(gpioCommand, ["in",node.pin,node.intype]);
}
node.running = true;
node.status({fill:"green",shape:"dot",text:"OK"});
node.child.stdout.on('data', function (data) {
data = data.toString().trim();
if (data.length > 0) {
if (node.buttonState !== -1) {
node.send({ topic:"pi/"+node.pin, payload:Number(data) });
}
node.buttonState = data;
node.status({fill:"green",shape:"dot",text:data});
if (RED.settings.verbose) { node.log("out: "+data+" :"); }
}
});
}, 250);
}
node.child.stderr.on('data', function (data) {
if (RED.settings.verbose) { node.log("err: "+data+" :"); }
});
node.child.on('close', function (code) {
if (RED.settings.verbose) { node.log("ret: "+code+" :"); }
node.child = null;
node.running = false;
node.status({fill:"red",shape:"circle",text:""});
});
node.child.on('error', function (err) {
if (err.errno === "ENOENT") { node.warn('Command not found'); }
else if (err.errno === "EACCES") { node.warn('Command not executable'); }
else { node.log('error: ' + err); }
});
}
else {
node.error("Invalid GPIO pin: "+node.pin);
}
node.on("close", function() {
clearInterval(node._interval);
if (node.child != null) {
node.child.stdin.write(" close "+node.pin);
node.child.kill('SIGKILL');
}
node.status({fill:"red",shape:"circle",text:""});
delete pinsInUse[node.pin];
if (RED.settings.verbose) { node.log("end"); }
});
}
RED.nodes.registerType("rpi-gpio in",GPIOInNode);
function GPIOOutNode(n) {
RED.nodes.createNode(this,n);
this.pin = pintable[n.pin];
this.pin = n.pin;
this.set = n.set || false;
this.level = n.level || 0;
this.out = n.out || "out";
var node = this;
(node.out === "pwm") ? (node.op = "pwm") : (node.op = "write");
if (node.pin !== undefined) {
exec(gpioCommand+" mode "+node.pin+" "+node.out, function(err,stdout,stderr) {
if (err) { node.error(err); }
else {
if (node.set && (node.out === "out")) {
exec(gpioCommand+" write "+node.pin+" "+node.level, function(err,stdout,stderr) {
if (err) { node.error(err); }
});
if (!pinsInUse.hasOwnProperty(this.pin)) {
pinsInUse[this.pin] = this.out;
}
node.on("input", function(msg) {
else {
if ((pinsInUse[this.pin] !== this.out)||(pinsInUse[this.pin] === "pwm")) {
node.error("GPIO pin "+this.pin+" already set as "+pinTypes[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);
var limit = 1;
if (node.out === "pwm") { limit = 1023; }
if (node.out === "pwm") { limit = 100; }
if ((out >= 0) && (out <= limit)) {
exec(gpioCommand+" "+node.op+" "+node.pin+" "+out, function(err,stdout,stderr) {
if (err) { node.error(err); }
});
if (RED.settings.verbose) { node.log("inp: "+msg.payload); }
if (node.child !== null) { node.child.stdin.write(msg.payload+"\n"); }
else { node.warn("Command not running"); }
node.status({fill:"green",shape:"dot",text:msg.payload});
}
else { node.warn("Invalid input: "+out); }
});
}
if (node.pin !== undefined) {
if (node.set && (node.out === "out")) {
node.child = spawn(gpioCommand, [node.out,node.pin,node.level]);
} else {
node.child = spawn(gpioCommand, [node.out,node.pin]);
}
node.running = true;
node.status({fill:"green",shape:"dot",text:"OK"});
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) {
if (RED.settings.verbose) { node.log("ret: "+code+" :"); }
node.child = null;
node.running = false;
node.status({fill:"red",shape:"circle",text:""});
});
node.child.on('error', function (err) {
if (err.errno === "ENOENT") { node.warn('Command not found'); }
else if (err.errno === "EACCES") { node.warn('Command not executable'); }
else { node.log('error: ' + err); }
});
}
else {
node.error("Invalid GPIO pin: "+node.pin);
}
node.on("close", function() {
exec(gpioCommand+" mode "+node.pin+" in");
if (node.child != null) {
node.child.stdin.write(" close "+node.pin);
node.child.kill('SIGKILL');
}
node.status({fill:"red",shape:"circle",text:""});
delete pinsInUse[node.pin];
if (RED.settings.verbose) { node.log("end"); }
});
}
var pitype = { type:"" };
exec(gpioCommand+" -v | grep Type", function(err,stdout,stderr) {
exec(gpioCommand+" rev 0", function(err,stdout,stderr) {
if (err) {
util.log('[36-rpi-gpio.js] Error: "'+gpioCommand+' -v" command failed for some reason.');
console.log('[rpi-gpio] Version command failed for some reason.');
}
else {
pitype = { type:(stdout.split(","))[0].split(": ")[1], rev:(stdout.split(","))[1].split(": ")[1] };
if (stdout.trim() == "0") { pitype = { type:"Compute" }; }
else if (stdout.trim() == "1") { pitype = { type:"A/B v1" }; }
else if (stdout.trim() == "2") { pitype = { type:"A/B v2" }; }
else if (stdout.trim() == "3") { pitype = { type:"Model B+" }; }
else { console.log("SAW Pi TYPE",stdout.trim()); }
}
});
RED.nodes.registerType("rpi-gpio in",GPIOInNode);
RED.nodes.registerType("rpi-gpio out",GPIOOutNode);
RED.httpAdmin.get('/rpi-gpio/:id',function(req,res) {
res.send( JSON.stringify(pitype) );
});
RED.httpAdmin.get('/rpi-pins/:id',function(req,res) {
res.send( JSON.stringify(pinsInUse) );
});
}

16
nodes/core/hardware/nrgpio Executable file
View File

@ -0,0 +1,16 @@
#
# Copyright 2014 IBM Corp.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
BASEDIR=$(dirname $0)
sudo python -u $BASEDIR/nrgpio.py $@

View File

@ -0,0 +1,125 @@
#
# Copyright 2014 IBM Corp.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Import library functions we need
import RPi.GPIO as GPIO
import sys
bounce = 20 # bounce time in mS to apply
if len(sys.argv) > 1:
cmd = sys.argv[1].lower()
pin = int(sys.argv[2])
GPIO.setmode(GPIO.BOARD)
GPIO.setwarnings(False)
if cmd == "pwm":
#print "Initialised pin "+str(pin)+" to PWM"
GPIO.setup(pin,GPIO.OUT)
p = GPIO.PWM(pin, 100)
p.start(0)
while True:
try:
data = raw_input()
if data == "close":
GPIO.cleanup(pin)
sys.exit(0)
p.ChangeDutyCycle(float(data))
except EOFError: # hopefully always caused by us sigint'ing the program
GPIO.cleanup(pin)
sys.exit(0)
except Exception as ex:
print "bad data: "+data
if cmd == "buzz":
#print "Initialised pin "+str(pin)+" to Buzz"
GPIO.setup(pin,GPIO.OUT)
p = GPIO.PWM(pin, 100)
p.stop()
while True:
try:
data = raw_input()
if data == "close":
GPIO.cleanup(pin)
sys.exit(0)
elif float(data) == 0:
p.stop()
else:
p.start(50)
p.ChangeFrequency(float(data))
except EOFError: # hopefully always caused by us sigint'ing the program
GPIO.cleanup(pin)
sys.exit(0)
except Exception as ex:
print "bad data: "+data
elif cmd == "out":
#print "Initialised pin "+str(pin)+" to OUT"
GPIO.setup(pin,GPIO.OUT)
if len(sys.argv) == 4:
GPIO.output(pin,int(sys.argv[3]))
while True:
try:
data = raw_input()
if data == "close":
GPIO.cleanup(pin)
sys.exit(0)
data = int(data)
except EOFError: # hopefully always caused by us sigint'ing the program
GPIO.cleanup(pin)
sys.exit(0)
except:
data = 0
if data != 0:
data = 1
GPIO.output(pin,data)
elif cmd == "in":
#print "Initialised pin "+str(pin)+" to IN"
def handle_callback(chan):
print GPIO.input(chan)
if len(sys.argv) == 4:
if sys.argv[3].lower() == "up":
GPIO.setup(pin,GPIO.IN,GPIO.PUD_UP)
elif sys.argv[3].lower() == "down":
GPIO.setup(pin,GPIO.IN,GPIO.PUD_DOWN)
else:
GPIO.setup(pin,GPIO.IN)
else:
GPIO.setup(pin,GPIO.IN)
print GPIO.input(pin)
GPIO.add_event_detect(pin, GPIO.BOTH, callback=handle_callback, bouncetime=bounce)
while True:
try:
data = raw_input()
if data == "close":
GPIO.cleanup(pin)
sys.exit(0)
except EOFError: # hopefully always caused by us sigint'ing the program
GPIO.cleanup(pin)
sys.exit(0)
elif cmd == "rev":
print GPIO.RPI_REVISION
elif cmd == "ver":
print GPIO.VERSION
else:
print "Bad parameters - {in|out|pwm} {pin} {value|up|down}"