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

New Pibrella node - Can use softpwm on all outputs.

Buzzer no longer breaks audio.
This commit is contained in:
Dave C-J 2014-12-26 15:24:21 +00:00
parent 3d8a510bae
commit 0ff8648258
6 changed files with 330 additions and 124 deletions

View File

@ -41,8 +41,7 @@
<script type="text/x-red" data-help-name="rpi-pibrella in">
<p>Raspberry Pi Pibrella input node. Generates a <b>msg.payload</b> with either a 0 or 1 depending on the state of the input pin.</p>
<p>The <b>msg.topic</b> is set to <i>pibrella/{the pin id}</i>, A, B, C, D or R</p>
<p><b>Note:</b> This node currently polls the pin every 250mS. This is not ideal as it loads the cpu.</p>
<p>Requires the WiringPi gpio command in order to work.</p>
<p>Requires the RPi.GPIO python library version 0.5.8 (or better) in order to work.</p>
</script>
<script type="text/javascript">
@ -82,6 +81,13 @@
<option value="Buzzer ">Buzzer</option>
</select>
</div>
<div class="form-row" id="node-set-pwm">
<label>&nbsp;&nbsp;&nbsp;&nbsp;Type</label>
<select id="node-input-out" style="width: 250px;">
<option value="out">Digital output</option>
<option value="pwm">PWM output</option>
</select>
</div>
<div class="form-row" id="node-set-check">
<label>&nbsp;</label>
<input type="checkbox" id="node-input-set" style="display: inline-block; width: auto; vertical-align: top;">
@ -99,15 +105,15 @@
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
<div class="form-tips" id="node-buzz-tip">Buzzer takes <b>msg.payload</b> between 2 (high) and 512 (low), or 0 for off.</div>
<div class="form-tips" id="node-buzz-tip">Buzzer expects <b>msg.payload</b> to be a number in Hz.</div>
</script>
<script type="text/x-red" data-help-name="rpi-pibrella out">
<p>Raspberry Pi Pibrella output node. The Pibrella board must be fitted.</p>
<p>Will set the selected output high (on) or low (off) depending on the value passed in. Expects a <b>msg.payload</b> with either a 0 or 1 (or true or false).</p>
<p>The Buzzer is a divider so low numbers are high notes. 0 is off, and the sensible lowest note is around 250-300. 2 is the highest note. 1 is just a buzz (so you can use 0/1 type inputs).</p>
<p><b>Note:</b> Using the buzzer is known to "kill" audio output via the 3.5mm socket.</p>
<p>Requires the WiringPi gpio command in order to work.</p>
<p>In PWM mode you can dim the onboard LEDs - expects a number from 0 - 100 (%).</p>
<p>The Buzzer takes a number in Hz (up to about 4000), or 0 for off, and 1 is a shortcut for 262.</div></p>
<p>Requires the RPi.GPIO python library version 0.5.8 (or better) in order to work.</p>
</script>
<script type="text/javascript">
@ -118,7 +124,8 @@
name: { value:"" },
pin: { value:"",required:true,validate:RED.validators.regex(/ /) },
set: { value:false },
level: { value:"0" }
level: { value:"0" },
out: { value:"out" }
},
inputs:1,
outputs:0,
@ -141,9 +148,12 @@
$("#node-input-pin").change(function() {
if ($('#node-input-pin').val() !== "Buzzer ") {
$("#node-set-check").show();
$("#node-set-pwm").show();
$("#node-buzz-tip").hide();
} else {
$("#node-set-check").hide();
$("#node-set-pwm").hide();
$("#node-set-state").hide();
$("#node-buzz-tip").show();
}
$("#node-input-set").change();

View File

@ -18,48 +18,60 @@ 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 = __dirname+'/nrgpio';
if (!fs.existsSync("/dev/ttyAMA0")) { // unlikely if not on a Pi
throw "Info : Ignoring Raspberry Pi specific node.";
//util.log("Info : Ignoring Raspberry Pibrella specific node.");
throw "Info : Ignoring Raspberry Pibrella specific node.";
}
if (!fs.existsSync("/usr/local/bin/gpio")) { // 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-pibrella] Info : Can't find RPi.GPIO python library.");
throw "Warning : Can't find RPi.GPIO python library.";
}
// Map physical P1 pins to Gordon's Wiring-Pi Pins (as they should be V1/V2 tolerant)
if ( !(1 & parseInt ((fs.statSync(gpioCommand).mode & parseInt ("777", 8)).toString (8)[0]) )) {
util.log("[rpi-pibrella] 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"};
var pintable = {
// Physical : WiringPi
"Amber LED":"0",
"Buzzer ":"1",
"Red LED":"2",
"Out E":"3",
"Out F":"4",
"Out G":"5",
"Out H":"6",
// Name : Pin
"Amber LED":"11",
"Buzzer ":"12",
"Red LED":"13",
"Out E":"15",
"Out F":"16",
"Out G":"18",
"Out H":"22",
"Green LED":"7",
"In C":"10",
"In B":"11",
"In D":"12",
"In A":"13",
"Red Button":"14",
"In D":"19",
"In A":"21",
"In B":"26",
"In C":"24",
"Red Button":"23"
}
var tablepin = {
// WiringPi : Physical
"0":"Amber",
"1":"Buzzer",
"2":"Red",
"3":"E",
"4":"F",
"5":"G",
"6":"H",
// Pin : Name
"11":"Amber",
"12":"Buzzer",
"13":"Red",
"15":"E",
"16":"F",
"18":"G",
"22":"H",
"7":"Green",
"10":"C",
"11":"B",
"12":"D",
"13":"A",
"14":"R",
"19":"D",
"21":"A",
"26":"B",
"24":"C",
"23":"R"
}
function PibrellaIn(n) {
@ -69,113 +81,157 @@ module.exports = function(RED) {
this.read = n.read || false;
if (this.read) { this.buttonState = -2; }
var node = this;
if (!pinsInUse.hasOwnProperty(this.pin)) {
pinsInUse[this.pin] = "tri";
}
else {
if ((pinsInUse[this.pin] !== "tri")||(pinsInUse[this.pin] === "pwm")) {
node.error("GPIO pin "+this.pin+" already set as "+pinTypes[pinsInUse[this.pin]]);
}
}
if (node.pin) {
exec("gpio mode "+node.pin+" in", function(err,stdout,stderr) {
if (err) { node.error(err); }
else {
node._interval = setInterval( function() {
exec("gpio 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:"pibrella/"+tablepin[node.pin], payload:node.buttonState};
node.send(msg);
}
if (node.pin !== undefined) {
node.child = spawn(gpioCommand, ["in",node.pin]);
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:"pibrella/"+tablepin[node.pin], payload:Number(data) });
}
node.buttonState = data;
node.status({fill:"green",shape:"dot",text:data});
if (RED.settings.verbose) { node.log("out: "+data+" :"); }
}
});
}, 200);
}
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-pibrella in",PibrellaIn);
function PibrellaOut(n) {
RED.nodes.createNode(this,n);
this.pin = pintable[n.pin];
this.set = n.set || false;
this.level = n.level || 0;
this.out = n.out || "out";
var node = this;
if (node.pin == "1") {
exec("gpio mode 1 pwm");
process.nextTick(function() {
exec("gpio pwm-ms");
node.on("input", function(msg) {
var out = Number(msg.payload);
if (out == 1) { // fixed buzz
exec("gpio pwm 1 511");
exec("gpio pwmc 100");
if (!pinsInUse.hasOwnProperty(this.pin)) {
pinsInUse[this.pin] = this.out;
}
else if ((out >= 2) && (out <= 9999)) { // set buzz to a value
exec("gpio pwm 1 511");
exec("gpio pwmc "+out);
}
else { exec("gpio pwm 1 0"); } // turn it off
});
});
}
else if (node.pin) {
exec("gpio mode "+node.pin+" out", function(err,stdout,stderr) {
if (err) { node.error(err); }
else {
if (node.set) {
exec("gpio write "+node.pin+" "+node.level, function(err,stdout,stderr) {
if (err) { node.error(err); }
});
if ((pinsInUse[this.pin] !== this.out)||(pinsInUse[this.pin] === "pwm")) {
node.error("GPIO pin "+this.pin+" already set as "+pinTypes[pinsInUse[this.pin]]);
}
node.on("input", function(msg) {
}
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)) {
exec("gpio write "+node.pin+" "+out, function(err,stdout,stderr) {
if (err) { node.error(err); }
});
var limit = 1;
if (node.out === "pwm") { limit = 100; }
if (node.pin === "12") {
limit = 4096;
if (out === 1) { out = 262; }
}
else { node.warn("Invalid input - not 0 or 1"); }
});
if ((out >= 0) && (out <= limit)) {
if (RED.settings.verbose) { node.log("inp: "+msg.payload); }
if (node.child !== null) { node.child.stdin.write(out+"\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.pin === "12") {
node.child = spawn(gpioCommand, ["buzz",node.pin]);
} else {
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("gpio 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"); }
});
}
RED.nodes.registerType("rpi-pibrella out",PibrellaOut);
RED.httpAdmin.get('/rpi-pibpins/:id',function(req,res) {
res.send( JSON.stringify(pinsInUse) );
});
}
//exec("gpio mode 0 out",function(err,stdout,stderr) {
//if (err) {
//util.log('[36-rpi-gpio.js] Error: "gpio" command failed for some reason.');
//}
//exec("gpio mode 1 out");
//exec("gpio mode 2 out");
//exec("gpio mode 3 out");
//exec("gpio mode 4 out");
//exec("gpio mode 5 out");
//exec("gpio mode 6 out");
//exec("gpio mode 7 out");
//exec("gpio mode 10 in");
//exec("gpio mode 11 in");
//exec("gpio mode 12 in");
//exec("gpio mode 13 in");
//exec("gpio mode 14 in");
//});
RED.nodes.registerType("rpi-pibrella in",PibrellaIn);
RED.nodes.registerType("rpi-pibrella out",PibrellaOut);
}

View File

@ -13,8 +13,9 @@ Run the following command in the root directory of your Node-RED install
Pre-reqs
--------
Requires the WiringPi gpio command to be installed in order to work. See the <a href="http://wiringpi.com" target="new">WiringPi site</a> for details on how to do this.
Requires the RPi.PIO python library version 0.5.8 (or better) in order to work. See the <a href="https://pypi.python.org/pypi/RPi.GPIO" target="new">RPi.GPIO site</a> for mode details.
sudo apt-get -y install python-rpi.gpio
Usage
-----
@ -25,14 +26,12 @@ A pair of input and output Node-RED nodes for the Raspberry Pi Pibrella from Pim
The output node will set the selected output high (on) or low (off) depending on the value passed in. Expects a <b>msg.payload</b> with either a 0 or 1 (or true or false).
The Buzzer is a divider so low numbers are high notes. 0 is off, and the sensible lowest note is around 250-300. 2 is the highest note. 1 is just a buzz - so you can use 0/1 type inputs.
You may also select PWM mode to dim the on board LEDs if you wish. Expects a value from 0 to 100.
**Note:** Using the buzzer is known to "kill" audio output via the 3.5mm socket.
The Buzzer expects a number representing the frequency in Hz. 0 is off and 1 is a tone - so you can use 0/1 type inputs as well.
###Input
The input node generates a <b>msg.payload</b> with either a 0 or 1 depending on the state of the input pin.
The <b>msg.topic</b> is set to <i>pibrella/{the pin id}</i> - which will be A, B, C, D or R.
<b>Note:</b> This node currently polls the pin every 250mS. This is not ideal as it loads the cpu.

16
hardware/Pibrella/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 $@

125
hardware/Pibrella/nrgpio.py Normal file
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}"

View File

@ -1,6 +1,6 @@
{
"name" : "node-red-node-pibrella",
"version" : "0.0.4",
"version" : "0.0.5",
"description" : "A Node-RED node to read from and write to a Pibrella Raspberry Pi add-on board",
"dependencies" : {
},