From a172c8178d1b611a99cca3017c57675c757a7297 Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Mon, 20 May 2019 19:35:05 +0100 Subject: [PATCH] Move Pi GPIO nodes to this repo --- hardware/PiGpio/36-rpi-gpio.html | 544 ++++++++++++++++++ hardware/PiGpio/36-rpi-gpio.js | 371 ++++++++++++ hardware/PiGpio/LICENSE | 177 ++++++ hardware/PiGpio/locales/de/36-rpi-gpio.html | 74 +++ .../PiGpio/locales/en-US/36-rpi-gpio.json | 74 +++ hardware/PiGpio/locales/ja/36-rpi-gpio.html | 71 +++ hardware/PiGpio/locales/ko/36-rpi-gpio.html | 71 +++ hardware/PiGpio/nrgpio | 17 + hardware/PiGpio/nrgpio.py | 239 ++++++++ hardware/PiGpio/package.json | 28 + test/hardware/PiGpio/36-rpi-gpio_spec.js | 157 +++++ 11 files changed, 1823 insertions(+) create mode 100644 hardware/PiGpio/36-rpi-gpio.html create mode 100644 hardware/PiGpio/36-rpi-gpio.js create mode 100644 hardware/PiGpio/LICENSE create mode 100644 hardware/PiGpio/locales/de/36-rpi-gpio.html create mode 100644 hardware/PiGpio/locales/en-US/36-rpi-gpio.json create mode 100644 hardware/PiGpio/locales/ja/36-rpi-gpio.html create mode 100644 hardware/PiGpio/locales/ko/36-rpi-gpio.html create mode 100755 hardware/PiGpio/nrgpio create mode 100755 hardware/PiGpio/nrgpio.py create mode 100644 hardware/PiGpio/package.json create mode 100644 test/hardware/PiGpio/36-rpi-gpio_spec.js diff --git a/hardware/PiGpio/36-rpi-gpio.html b/hardware/PiGpio/36-rpi-gpio.html new file mode 100644 index 00000000..e6cd8701 --- /dev/null +++ b/hardware/PiGpio/36-rpi-gpio.html @@ -0,0 +1,544 @@ + + + + + + + + + + + + + + + + diff --git a/hardware/PiGpio/36-rpi-gpio.js b/hardware/PiGpio/36-rpi-gpio.js new file mode 100644 index 00000000..9afcd40d --- /dev/null +++ b/hardware/PiGpio/36-rpi-gpio.js @@ -0,0 +1,371 @@ + +module.exports = function(RED) { + "use strict"; + var exec = require('child_process').exec; + var spawn = require('child_process').spawn; + var fs = require('fs'); + + var gpioCommand = __dirname+'/nrgpio'; + var allOK = true; + + try { + var cpuinfo = fs.readFileSync("/proc/cpuinfo").toString(); + if (cpuinfo.indexOf(": BCM") === -1) { + allOK = false; + RED.log.warn("rpi-gpio : "+RED._("rpi-gpio.errors.ignorenode")); + } + try { + fs.statSync("/usr/share/doc/python-rpi.gpio"); // test on Raspbian + // /usr/lib/python2.7/dist-packages/RPi/GPIO + } catch(err) { + try { + fs.statSync("/usr/lib/python2.7/site-packages/RPi/GPIO"); // test on Arch + } catch(err) { + try { + fs.statSync("/usr/lib/python2.7/dist-packages/RPi/GPIO"); // test on Hypriot + } catch(err) { + try { + fs.statSync("/usr/local/lib/python2.7/dist-packages/RPi/GPIO"); // installed with pip + } catch(err) { + RED.log.warn("rpi-gpio : "+RED._("rpi-gpio.errors.libnotfound")); + allOK = false; + } + } + } + } + if ( !(1 & parseInt((fs.statSync(gpioCommand).mode & parseInt("777", 8)).toString(8)[0]) )) { + RED.log.warn("rpi-gpio : "+RED._("rpi-gpio.errors.needtobeexecutable",{command:gpioCommand})); + allOK = false; + } + } catch(err) { + allOK = false; + RED.log.warn("rpi-gpio : "+RED._("rpi-gpio.errors.ignorenode")); + } + + // the magic to make python print stuff immediately + process.env.PYTHONUNBUFFERED = 1; + + var pinsInUse = {}; + var pinTypes = {"out":RED._("rpi-gpio.types.digout"), "tri":RED._("rpi-gpio.types.input"), "up":RED._("rpi-gpio.types.pullup"), "down":RED._("rpi-gpio.types.pulldown"), "pwm":RED._("rpi-gpio.types.pwmout")}; + + function GPIOInNode(n) { + RED.nodes.createNode(this,n); + this.buttonState = -1; + this.pin = n.pin; + this.intype = n.intype; + this.read = n.read || false; + this.debounce = Number(n.debounce || 25); + 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.warn(RED._("rpi-gpio.errors.alreadyset",{pin:this.pin,type:pinTypes[pinsInUse[this.pin]]})); + } + } + + if (allOK === true) { + if (node.pin !== undefined) { + node.child = spawn(gpioCommand, ["in",node.pin,node.intype,node.debounce]); + node.running = true; + node.status({fill:"green",shape:"dot",text:"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:"pi/"+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._("rpi-gpio.status.closed")); } + if (node.done) { + node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"}); + node.done(); + } + else { node.status({fill:"red",shape:"ring",text:"rpi-gpio.status.stopped"}); } + }); + + node.child.on('error', function (err) { + if (err.errno === "ENOENT") { node.error(RED._("rpi-gpio.errors.commandnotfound")); } + else if (err.errno === "EACCES") { node.error(RED._("rpi-gpio.errors.commandnotexecutable")); } + else { node.error(RED._("rpi-gpio.errors.error",{error:err.errno})) } + }); + + } + else { + node.warn(RED._("rpi-gpio.errors.invalidpin")+": "+node.pin); + } + } + else { + node.status({fill:"grey",shape:"dot",text:"node-red:rpi-gpio.status.not-available"}); + if (node.read === true) { + var val; + if (node.intype == "up") { val = 1; } + if (node.intype == "down") { val = 0; } + setTimeout(function(){ + node.send({ topic:"pi/"+node.pin, payload:val }); + node.status({fill:"grey",shape:"dot",text:RED._("rpi-gpio.status.na",{value:val})}); + },250); + } + } + + node.on("close", function(done) { + node.status({fill:"grey",shape:"ring",text:"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("rpi-gpio in",GPIOInNode); + + function GPIOOutNode(n) { + RED.nodes.createNode(this,n); + this.pin = n.pin; + this.set = n.set || false; + this.level = n.level || 0; + this.freq = n.freq || 100; + this.out = n.out || "out"; + var node = this; + if (!pinsInUse.hasOwnProperty(this.pin)) { + pinsInUse[this.pin] = this.out; + } + else { + if ((pinsInUse[this.pin] !== this.out)||(pinsInUse[this.pin] === "pwm")) { + node.warn(RED._("rpi-gpio.errors.alreadyset",{pin:this.pin,type: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("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._("rpi-gpio.errors.pythoncommandnotfound"),msg); + node.status({fill:"red",shape:"ring",text:"rpi-gpio.status.not-running"}); + } + } + else { node.warn(RED._("rpi-gpio.errors.invalidinput")+": "+out); } + } + + if (allOK === true) { + if (node.pin !== undefined) { + if (node.set && (node.out === "out")) { + node.child = spawn(gpioCommand, [node.out,node.pin,node.level]); + node.status({fill:"green",shape:"dot",text:node.level}); + } else { + node.child = spawn(gpioCommand, [node.out,node.pin,node.freq]); + node.status({fill:"green",shape:"dot",text:"common.status.ok"}); + } + 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._("rpi-gpio.status.closed")); } + if (node.done) { + node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"}); + node.done(); + } + else { node.status({fill:"red",shape:"ring",text:"rpi-gpio.status.stopped"}); } + }); + + node.child.on('error', function (err) { + if (err.errno === "ENOENT") { node.error(RED._("rpi-gpio.errors.commandnotfound")); } + else if (err.errno === "EACCES") { node.error(RED._("rpi-gpio.errors.commandnotexecutable")); } + else { node.error(RED._("rpi-gpio.errors.error")+': ' + err.errno); } + }); + + } + else { + node.warn(RED._("rpi-gpio.errors.invalidpin")+": "+node.pin); + } + } + else { + node.status({fill:"grey",shape:"dot",text:"node-red:rpi-gpio.status.not-available"}); + node.on("input", function(msg){ + node.status({fill:"grey",shape:"dot",text:RED._("rpi-gpio.status.na",{value:msg.payload.toString()})}); + }); + } + + node.on("close", function(done) { + node.status({fill:"grey",shape:"ring",text:"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("rpi-gpio out",GPIOOutNode); + + function PiMouseNode(n) { + RED.nodes.createNode(this,n); + this.butt = n.butt || 7; + var node = this; + + if (allOK === true) { + node.child = spawn(gpioCommand+".py", ["mouse",node.butt]); + node.status({fill:"green",shape:"dot",text:"common.status.ok"}); + + node.child.stdout.on('data', function (data) { + data = Number(data); + if (data !== 0) { node.send({ topic:"pi/mouse", button:data, payload:1 }); } + else { node.send({ topic:"pi/mouse", button:data, payload:0 }); } + }); + + 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._("rpi-gpio.status.closed")); } + if (node.done) { + node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"}); + node.done(); + } + else { node.status({fill:"red",shape:"ring",text:"rpi-gpio.status.stopped"}); } + }); + + node.child.on('error', function (err) { + if (err.errno === "ENOENT") { node.error(RED._("rpi-gpio.errors.commandnotfound")); } + else if (err.errno === "EACCES") { node.error(RED._("rpi-gpio.errors.commandnotexecutable")); } + else { node.error(RED._("rpi-gpio.errors.error")+': ' + err.errno); } + }); + + node.on("close", function(done) { + node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"}); + if (node.child != null) { + node.done = done; + node.child.kill('SIGINT'); + node.child = null; + } + else { done(); } + }); + } + else { + node.status({fill:"grey",shape:"dot",text:"node-red:rpi-gpio.status.not-available"}); + } + } + RED.nodes.registerType("rpi-mouse",PiMouseNode); + + function PiKeyboardNode(n) { + RED.nodes.createNode(this,n); + var node = this; + + if (allOK === true) { + node.child = spawn(gpioCommand+".py", ["kbd","0"]); + node.status({fill:"green",shape:"dot",text:"common.status.ok"}); + + node.child.stdout.on('data', function (data) { + var b = data.toString().trim().split(","); + var act = "up"; + if (b[1] === "1") { act = "down"; } + if (b[1] === "2") { act = "repeat"; } + node.send({ topic:"pi/key", payload:Number(b[0]), action:act }); + }); + + 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._("rpi-gpio.status.closed")); } + if (node.done) { + node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"}); + node.done(); + } + else { node.status({fill:"red",shape:"ring",text:"rpi-gpio.status.stopped"}); } + }); + + node.child.on('error', function (err) { + if (err.errno === "ENOENT") { node.error(RED._("rpi-gpio.errors.commandnotfound")); } + else if (err.errno === "EACCES") { node.error(RED._("rpi-gpio.errors.commandnotexecutable")); } + else { node.error(RED._("rpi-gpio.errors.error")+': ' + err.errno); } + }); + + node.on("close", function(done) { + node.status({}); + if (node.child != null) { + node.done = done; + node.child.kill('SIGINT'); + node.child = null; + } + else { done(); } + }); + } + else { + node.status({fill:"grey",shape:"dot",text:"node-red:rpi-gpio.status.not-available"}); + } + } + RED.nodes.registerType("rpi-keyboard",PiKeyboardNode); + + var pitype = { type:"" }; + if (allOK === true) { + exec(gpioCommand+" info", function(err,stdout,stderr) { + if (err) { + RED.log.info(RED._("rpi-gpio.errors.version")); + } + else { + try { + var info = JSON.parse( stdout.trim().replace(/\'/g,"\"") ); + pitype.type = info["TYPE"]; + } + catch(e) { + RED.log.info(RED._("rpi-gpio.errors.sawpitype"),stdout.trim()); + } + } + }); + } + + RED.httpAdmin.get('/rpi-gpio/:id', RED.auth.needsPermission('rpi-gpio.read'), function(req,res) { + res.json(pitype); + }); + + RED.httpAdmin.get('/rpi-pins/:id', RED.auth.needsPermission('rpi-gpio.read'), function(req,res) { + res.json(pinsInUse); + }); +} diff --git a/hardware/PiGpio/LICENSE b/hardware/PiGpio/LICENSE new file mode 100644 index 00000000..f433b1a5 --- /dev/null +++ b/hardware/PiGpio/LICENSE @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/hardware/PiGpio/locales/de/36-rpi-gpio.html b/hardware/PiGpio/locales/de/36-rpi-gpio.html new file mode 100644 index 00000000..89d9e4a6 --- /dev/null +++ b/hardware/PiGpio/locales/de/36-rpi-gpio.html @@ -0,0 +1,74 @@ + + + + + + + + + diff --git a/hardware/PiGpio/locales/en-US/36-rpi-gpio.json b/hardware/PiGpio/locales/en-US/36-rpi-gpio.json new file mode 100644 index 00000000..4ce96bc2 --- /dev/null +++ b/hardware/PiGpio/locales/en-US/36-rpi-gpio.json @@ -0,0 +1,74 @@ +{ + "rpi-gpio": { + "label": { + "gpiopin": "GPIO", + "selectpin": "select pin", + "resistor": "Resistor?", + "readinitial": "Read initial state of pin on deploy/restart?", + "type": "Type", + "initpin": "Initialise pin state?", + "debounce": "Debounce", + "freq": "Frequency", + "button": "Button", + "pimouse": "Pi Mouse", + "pikeyboard": "Pi Keyboard", + "left": "Left", + "right": "Right", + "middle": "Middle" + }, + "resistor": { + "none": "none", + "pullup": "pullup", + "pulldown": "pulldown" + }, + "digout": "Digital output", + "pwmout": "PWM output", + "servo": "Servo output", + "initpin0": "initial level of pin - low (0)", + "initpin1": "initial level of pin - high (1)", + "left": "left", + "right": "right", + "middle": "middle", + "any": "any", + "pinname": "Pin", + "alreadyuse": "already in use", + "alreadyset": "already set as", + "tip": { + "pin": "Pins in Use: ", + "in": "Tip: Only Digital Input is supported - input must be 0 or 1.", + "dig": "Tip: For digital output - input must be 0 or 1.", + "pwm": "Tip: For PWM output - input must be between 0 to 100; setting high frequency might occupy more CPU than expected.", + "ser": "Tip: For Servo output - input must be between 0 to 100. 50 is centre." + }, + "types": { + "digout": "digital output", + "input": "input", + "pullup": "input with pull up", + "pulldown": "input with pull down", + "pwmout": "PWM output", + "servo": "Servo output" + }, + "status": { + "stopped": "stopped", + "closed": "closed", + "not-running": "not running", + "not-available": "not available", + "na": "N/A : __value__" + }, + "errors": { + "ignorenode": "Raspberry Pi specific node set inactive", + "version": "Failed to get version from Pi", + "sawpitype": "Saw Pi Type", + "libnotfound": "Cannot find Pi RPi.GPIO python library", + "alreadyset": "GPIO pin __pin__ already set as type: __type__", + "invalidpin": "Invalid GPIO pin", + "invalidinput": "Invalid input", + "needtobeexecutable": "__command__ needs to be executable", + "mustbeexecutable": "nrgpio must to be executable", + "commandnotfound": "nrgpio command not found", + "commandnotexecutable": "nrgpio command not executable", + "error": "error: __error__", + "pythoncommandnotfound": "nrpgio python command not running" + } + } +} diff --git a/hardware/PiGpio/locales/ja/36-rpi-gpio.html b/hardware/PiGpio/locales/ja/36-rpi-gpio.html new file mode 100644 index 00000000..c84f5c13 --- /dev/null +++ b/hardware/PiGpio/locales/ja/36-rpi-gpio.html @@ -0,0 +1,71 @@ + + + + + + + + + diff --git a/hardware/PiGpio/locales/ko/36-rpi-gpio.html b/hardware/PiGpio/locales/ko/36-rpi-gpio.html new file mode 100644 index 00000000..dfcde83d --- /dev/null +++ b/hardware/PiGpio/locales/ko/36-rpi-gpio.html @@ -0,0 +1,71 @@ + + + + + + + + + diff --git a/hardware/PiGpio/nrgpio b/hardware/PiGpio/nrgpio new file mode 100755 index 00000000..d81fcf44 --- /dev/null +++ b/hardware/PiGpio/nrgpio @@ -0,0 +1,17 @@ +#!/bin/bash +# +# Copyright JS Foundation and other contributors, http://js.foundation +# +# 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) +python -u $BASEDIR/nrgpio.py $@ diff --git a/hardware/PiGpio/nrgpio.py b/hardware/PiGpio/nrgpio.py new file mode 100755 index 00000000..7908ca11 --- /dev/null +++ b/hardware/PiGpio/nrgpio.py @@ -0,0 +1,239 @@ +#!/usr/bin/python +# +# Copyright JS Foundation and other contributors, http://js.foundation +# +# 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 struct +import sys +import os +import subprocess +from time import sleep + +try: + raw_input # Python 2 +except NameError: + raw_input = input # Python 3 + +bounce = 25 + +if len(sys.argv) > 2: + 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") + try: + freq = int(sys.argv[3]) + except: + freq = 100 + + GPIO.setup(pin,GPIO.OUT) + p = GPIO.PWM(pin, freq) + p.start(0) + + while True: + try: + data = raw_input() + if 'close' in data: + sys.exit(0) + p.ChangeDutyCycle(float(data)) + except (EOFError, SystemExit): # 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 == "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 'close' in data: + sys.exit(0) + elif float(data) == 0: + p.stop() + else: + p.start(50) + p.ChangeFrequency(float(data)) + except (EOFError, SystemExit): # 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 'close' in data: + sys.exit(0) + data = int(data) + except (EOFError, SystemExit): # hopefully always caused by us sigint'ing the program + GPIO.cleanup(pin) + sys.exit(0) + except: + if len(sys.argv) == 4: + data = int(sys.argv[3]) + else: + data = 0 + if data != 0: + data = 1 + GPIO.output(pin,data) + + elif cmd == "in": + #print("Initialised pin "+str(pin)+" to IN") + bounce = float(sys.argv[4]) + def handle_callback(chan): + sleep(bounce/1000.0) + print(GPIO.input(chan)) + + 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) + + print(GPIO.input(pin)) + GPIO.add_event_detect(pin, GPIO.BOTH, callback=handle_callback, bouncetime=int(bounce)) + + while True: + try: + data = raw_input() + if 'close' in data: + sys.exit(0) + except (EOFError, SystemExit): # hopefully always caused by us sigint'ing the program + GPIO.cleanup(pin) + sys.exit(0) + + elif cmd == "byte": + #print("Initialised BYTE mode - "+str(pin)+) + list = [7,11,13,12,15,16,18,22] + GPIO.setup(list,GPIO.OUT) + + while True: + try: + data = raw_input() + if 'close' in data: + sys.exit(0) + data = int(data) + except (EOFError, SystemExit): # hopefully always caused by us sigint'ing the program + GPIO.cleanup() + sys.exit(0) + except: + data = 0 + for bit in range(8): + if pin == 1: + mask = 1 << (7 - bit) + else: + mask = 1 << bit + GPIO.output(list[bit], data & mask) + + elif cmd == "borg": + #print("Initialised BORG mode - "+str(pin)+) + GPIO.setup(11,GPIO.OUT) + GPIO.setup(13,GPIO.OUT) + GPIO.setup(15,GPIO.OUT) + r = GPIO.PWM(11, 100) + g = GPIO.PWM(13, 100) + b = GPIO.PWM(15, 100) + r.start(0) + g.start(0) + b.start(0) + + while True: + try: + data = raw_input() + if 'close' in data: + sys.exit(0) + c = data.split(",") + r.ChangeDutyCycle(float(c[0])) + g.ChangeDutyCycle(float(c[1])) + b.ChangeDutyCycle(float(c[2])) + except (EOFError, SystemExit): # hopefully always caused by us sigint'ing the program + GPIO.cleanup() + sys.exit(0) + except: + data = 0 + + elif cmd == "mouse": # catch mice button events + file = open( "/dev/input/mice", "rb" ) + oldbutt = 0 + + def getMouseEvent(): + global oldbutt + global pin + buf = file.read(3) + pin = pin & 0x07 + button = ord( buf[0] ) & pin # mask out just the required button(s) + if button != oldbutt: # only send if changed + oldbutt = button + print(button) + + while True: + try: + getMouseEvent() + except: + file.close() + sys.exit(0) + + elif cmd == "kbd": # catch keyboard button events + try: + while not os.path.isdir("/dev/input/by-path"): + sleep(10) + infile = subprocess.check_output("ls /dev/input/by-path/ | grep -m 1 'kbd'", shell=True).strip() + infile_path = "/dev/input/by-path/" + infile + EVENT_SIZE = struct.calcsize('llHHI') + file = open(infile_path, "rb") + event = file.read(EVENT_SIZE) + while event: + (tv_sec, tv_usec, type, code, value) = struct.unpack('llHHI', event) + #if type != 0 or code != 0 or value != 0: + if type == 1: + # type,code,value + print("%u,%u" % (code, value)) + event = file.read(EVENT_SIZE) + print("0,0") + file.close() + sys.exit(0) + except: + file.close() + sys.exit(0) + +elif len(sys.argv) > 1: + cmd = sys.argv[1].lower() + if cmd == "rev": + print(GPIO.RPI_REVISION) + elif cmd == "ver": + print(GPIO.VERSION) + elif cmd == "info": + print(GPIO.RPI_INFO) + else: + print("Bad parameters - in|out|pwm|buzz|byte|borg|mouse|kbd|ver|info {pin} {value|up|down}") + print(" only ver (gpio version) and info (board information) accept no pin parameter.") + +else: + print("Bad parameters - in|out|pwm|buzz|byte|borg|mouse|kbd|ver|info {pin} {value|up|down}") diff --git a/hardware/PiGpio/package.json b/hardware/PiGpio/package.json new file mode 100644 index 00000000..7626f4b8 --- /dev/null +++ b/hardware/PiGpio/package.json @@ -0,0 +1,28 @@ +{ + "name": "node-red-node-pi-gpio", + "version": "1.0.0", + "description": "The basic Node-RED node for Pi GPIO", + "dependencies" : { + }, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "https://github.com/node-red/node-red-nodes/tree/master/hardware/PiGpio" + }, + "keywords": [ + "node-red", "Pi", "GPIO", "PiGpio" + ], + "author": { + "name": "Dave Conway-Jones", + "email": "ceejay@vnet.ibm.com", + "url": "http://nodered.org" + }, + "license": "Apache-2.0", + "node-red" : { + "nodes": { + "rpi-gpio": "36-rpi-gpio.js" + } + } +} diff --git a/test/hardware/PiGpio/36-rpi-gpio_spec.js b/test/hardware/PiGpio/36-rpi-gpio_spec.js new file mode 100644 index 00000000..5f5ac75e --- /dev/null +++ b/test/hardware/PiGpio/36-rpi-gpio_spec.js @@ -0,0 +1,157 @@ +/** + * Copyright JS Foundation and other contributors, http://js.foundation + * + * 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. + **/ + +var should = require("should"); +var helper = require("node-red-node-test-helper"); +var rpiNode = require('../../../hardware/PiGpio/36-rpi-gpio.js'); + +//var rpiNode = require("nr-test-utils").require("@node-red/nodes/core/hardware/36-rpi-gpio.js"); +//var statusNode = require("nr-test-utils").require("@node-red/nodes/core/core/25-status.js"); +var helper = require("node-red-node-test-helper"); +var fs = require("fs"); + +describe('RPI GPIO Node', function() { + + before(function(done) { + helper.startServer(done); + }); + + after(function(done) { + helper.stopServer(done); + }); + + afterEach(function() { + helper.unload(); + }); + + var checkIgnore = function(done) { + setTimeout(function() { + try { + var logEvents = helper.log().args.filter(function(evt) { + return ((evt[0].level == 30) && (evt[0].msg.indexOf("rpi-gpio")===0)); + }); + logEvents[0][0].should.have.a.property('msg'); + logEvents[0][0].msg.toString().should.startWith("rpi-gpio : rpi-gpio.errors.ignorenode"); + done(); + } catch(err) { + done(err); + } + },25); + } + + it('should load Input node', function(done) { + var flow = [{id:"n1", type:"rpi-gpio in", name:"rpi-gpio in" }]; + helper.load(rpiNode, flow, function() { + var n1 = helper.getNode("n1"); + n1.should.have.property('name', 'rpi-gpio in'); + try { + var cpuinfo = fs.readFileSync("/proc/cpuinfo").toString(); + if (cpuinfo.indexOf(": BCM") === 1) { + done(); // It's ON a PI ... should really do more tests ! + } else { + checkIgnore(done); + } + } + catch(e) { + checkIgnore(done); + } + }); + }); + + it('should load Output node', function(done) { + var flow = [{id:"n1", type:"rpi-gpio out", name:"rpi-gpio out" }]; + helper.load(rpiNode, flow, function() { + var n1 = helper.getNode("n1"); + n1.should.have.property('name', 'rpi-gpio out'); + try { + var cpuinfo = fs.readFileSync("/proc/cpuinfo").toString(); + if (cpuinfo.indexOf(": BCM") === 1) { + done(); // It's ON a PI ... should really do more tests ! + } else { + checkIgnore(done); + } + } + catch(e) { + checkIgnore(done); + } + }); + }); + + + it('should read a dummy value high (not on Pi)', function(done) { + var flow = [{id:"n1", type:"rpi-gpio in", pin:"7", intype:"up", debounce:"25", read:true, wires:[["n2"]] }, + {id:"n2", type:"helper"}]; + helper.load(rpiNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + try { + msg.should.have.property('topic', 'pi/7'); + msg.should.have.property('payload', 1); + done(); + } catch(err) { + done(err); + } + }); + }); + }); + + it('should read a dummy value low (not on Pi)', function(done) { + var flow = [{id:"n1", type:"rpi-gpio in", pin:"11", intype:"down", debounce:"25", read:true, wires:[["n2"]] }, + {id:"n2", type:"helper"}]; + helper.load(rpiNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + try { + msg.should.have.property('topic', 'pi/11'); + msg.should.have.property('payload', 0); + done(); + } catch(err) { + done(err); + } + }); + }); + }); + + // it('should be able preset out to a dummy value (not on Pi)', function(done) { + // var flow = [{id:"n1", type:"rpi-gpio out", pin:"7", out:"out", level:"0", set:true, freq:"", wires:[], z:"1"}, + // {id:"n2", type:"status", scope:null, wires:[["n3"]], z:"1"}, + // {id:"n3", type:"helper", z:"1"}]; + // helper.load([rpiNode,statusNode], flow, function() { + // var n1 = helper.getNode("n1"); + // var n2 = helper.getNode("n2"); + // var n3 = helper.getNode("n3"); + // var count = 0; + // n3.on("input", function(msg) { + // // Only check the first status message received as it may get a + // // 'closed' status as the test is tidied up. + // if (count === 0) { + // count++; + // try { + // msg.should.have.property('status'); + // msg.status.should.have.property('text', "rpi-gpio.status.na"); + // done(); + // } catch(err) { + // done(err); + // } + // } + // }); + // n1.receive({payload:"1"}); + // }); + // }); + +});