From 9e4187d6a8d63ab8a7d56957a5bcc96305362f5e Mon Sep 17 00:00:00 2001 From: dceejay Date: Sat, 27 Dec 2014 13:11:44 +0000 Subject: [PATCH] New Pi GPIO node based on RPI.GPIO library. Adds PWM support of outputs and easier access to interrupts for inputs. --- nodes/core/hardware/36-rpi-gpio.html | 65 +++++-- nodes/core/hardware/36-rpi-gpio.js | 253 +++++++++++++++------------ nodes/core/hardware/nrgpio | 16 ++ nodes/core/hardware/nrgpio.py | 125 +++++++++++++ 4 files changed, 327 insertions(+), 132 deletions(-) create mode 100755 nodes/core/hardware/nrgpio create mode 100644 nodes/core/hardware/nrgpio.py diff --git a/nodes/core/hardware/36-rpi-gpio.html b/nodes/core/hardware/36-rpi-gpio.html index 0da27b77e..639e10bdb 100644 --- a/nodes/core/hardware/36-rpi-gpio.html +++ b/nodes/core/hardware/36-rpi-gpio.html @@ -45,7 +45,6 @@ -
@@ -63,11 +62,11 @@ + - @@ -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"); + } + pinnow = pinnew; } - else { - $('#node-set-pwm').hide(); - $('#node-input-out').val("out"); + }); + + $("#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"); } - }; - $("#node-input-pin").change(function () { hidepwm(); }); - hidepwm(); + }); var hidestate = function () { if ($("#node-input-out").val() === "pwm") { diff --git a/nodes/core/hardware/36-rpi-gpio.js b/nodes/core/hardware/36-rpi-gpio.js index 2a56ad17e..9e8048e03 100644 --- a/nodes/core/hardware/36-rpi-gpio.js +++ b/nodes/core/hardware/36-rpi-gpio.js @@ -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); - } - } - } - }); - }, 250); + 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+" :"); } } }); + + 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 (!pinsInUse.hasOwnProperty(this.pin)) { + pinsInUse[this.pin] = this.out; + } + 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 = 100; } + if ((out >= 0) && (out <= limit)) { + 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) { - 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); } - }); - } - node.on("input", function(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 ((out >= 0) && (out <= limit)) { - exec(gpioCommand+" "+node.op+" "+node.pin+" "+out, function(err,stdout,stderr) { - if (err) { node.error(err); } - }); - } - else { node.warn("Invalid input: "+out); } - }); - } + 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) ); + }); } diff --git a/nodes/core/hardware/nrgpio b/nodes/core/hardware/nrgpio new file mode 100755 index 000000000..88b381a7d --- /dev/null +++ b/nodes/core/hardware/nrgpio @@ -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 $@ diff --git a/nodes/core/hardware/nrgpio.py b/nodes/core/hardware/nrgpio.py new file mode 100644 index 000000000..122a0f5ee --- /dev/null +++ b/nodes/core/hardware/nrgpio.py @@ -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}"