diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 00000000..e006a3d7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,34 @@ + + +### Which node are you reporting an issue on? + +### What are the steps to reproduce? + +### What happens? + +### What do you expect to happen? + +### Please tell us about your environment: + +- [ ] Node-RED version: +- [ ] node.js version: +- [ ] npm version: +- [ ] Platform/OS: +- [ ] Browser: diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..526464a6 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,34 @@ + + +- [ ] Bugfix (non-breaking change which fixes an issue) +- [ ] New feature (non-breaking change which adds functionality) + + + +## Proposed changes + + + +## Checklist + + +- [ ] I have read the [contribution guidelines](https://github.com/node-red/node-red-nodes/blob/master/CONTRIBUTING.md) +- [ ] For non-bugfix PRs, I have discussed this change on the forum/slack team. +- [ ] I have run `grunt` to verify the unit tests pass +- [ ] I have added suitable unit tests to cover the new/changed functionality diff --git a/.gitignore b/.gitignore index 09ee0ffd..729dabf3 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,6 @@ coverage puball.sh setenv.sh /.project +package-lock.json +social/xmpp/92-xmpp.old +*.tgz diff --git a/.jshintrc b/.jshintrc index 81998b32..b16056c8 100644 --- a/.jshintrc +++ b/.jshintrc @@ -1,19 +1,15 @@ { - "asi": true, // allow missing semicolons - "curly": true, // require braces - "eqnull": true, // ignore ==null - //"eqeqeq": true, // enforce === - "freeze": true, // don't allow override - "indent": 4, // default indent of 4 - "forin": true, // require property filtering in "for in" loops - "immed": true, // require immediate functions to be wrapped in ( ) - "nonbsp": true, // warn on unexpected whitespace breaking chars - //"strict": true, // commented out for now as it causes 100s of warnings, but want to get there eventually - //"unused": true, // Check for unused functions and variables - "loopfunc": true, // allow functions to be defined in loops - //"expr": true, // allow ternery operator syntax... - "shadow": true, // allow variable shadowing (re-use of names...) - "sub": true, // don't warn that foo['bar'] should be written as foo.bar - "proto": true, // allow setting of __proto__ in node < v0.12 - "esversion": 6 // allow es6 + "asi": true, + "curly": true, + "eqnull": true, + "freeze": true, + "indent": 4, + "forin": true, + "immed": true, + "nonbsp": true, + "loopfunc": true, + "shadow": true, + "sub": true, + "proto": true, + "esversion": 8 } diff --git a/.travis.yml b/.travis.yml index 41485f0f..e7d7f89c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,27 +1,32 @@ sudo: false language: node_js -env: - - CXX="g++-4.8" -addons: - apt: - sources: - - ubuntu-toolchain-r-test - packages: - - g++-4.8 - - gcc-4.8 matrix: - allow_failures: - - node_js: "7" -node_js: - - "8" - - "7" - - "6" - - "4" + # allow_failures: + # - node_js: 14 + include: + - node_js: 14 + - node_js: 12 + - node_js: 10 + - node_js: 8 + # - python: 2.7 + # language: python + # before_script: pip install flake8 + # script: flake8 . --count --select=E901,E999,F821,F822,F823 --show-source --statistics + - python: 3.7 + language: python + dist: xenial # required for Python 3.7 (travis-ci/travis-ci#9069) + sudo: required # required for Python 3.7 (travis-ci/travis-ci#9069) + before_script: pip install flake8 + script: flake8 . --count --select=E901,E999,F821,F822,F823 --show-source --statistics +before_install: + - npm i -g npm before_script: - - npm install -g istanbul grunt-cli - - npm install coveralls - - npm install git+https://github.com/node-red/node-red.git - - export NODE_RED_HOME=`pwd`/node_modules/node-red - - (cd $NODE_RED_HOME ; npm install nock@~0.48.0) + # Remove the './node_modules/.bin:' entry, see https://github.com/travis-ci/travis-ci/issues/8813 + - export PATH=`echo ${PATH} | sed -re 's,(^|:)(./)?node_modules/.bin($|:),\1,'` + - npm install -g nyc grunt-cli coveralls + - npm install node-red script: - - istanbul cover grunt --report lcovonly && istanbul report text && ( cat coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js || true ) && rm -rf coverage + # - istanbul cover grunt --report lcovonly && istanbul report text && ( cat coverage/lcov.info | $(npm get prefix)/bin/coveralls || true ) && rm -rf coverage + # - nyc --check-coverage --reporter=text --reporter=lcovonly --reporter=html grunt + # - nyc grunt && rm -rf coverage .nyc_output + - nyc grunt && nyc report --reporter=text-lcov | coveralls diff --git a/99-sample.html.demo b/99-sample.html.demo index 67c25724..c75fee17 100644 --- a/99-sample.html.demo +++ b/99-sample.html.demo @@ -6,7 +6,7 @@ - diff --git a/analysis/mlsentiment/locales/ja/mlsentiment.json b/analysis/mlsentiment/locales/ja/mlsentiment.json new file mode 100644 index 00000000..6763ab85 --- /dev/null +++ b/analysis/mlsentiment/locales/ja/mlsentiment.json @@ -0,0 +1,8 @@ +{ + "mlsentiment": { + "sentiment": "sentiment", + "label": { + "language": "言語" + } + } +} diff --git a/analysis/mlsentiment/mlsentiment.html b/analysis/mlsentiment/mlsentiment.html new file mode 100644 index 00000000..d6b75a8e --- /dev/null +++ b/analysis/mlsentiment/mlsentiment.html @@ -0,0 +1,176 @@ + + + + + + diff --git a/analysis/mlsentiment/mlsentiment.js b/analysis/mlsentiment/mlsentiment.js new file mode 100644 index 00000000..f9bb85fa --- /dev/null +++ b/analysis/mlsentiment/mlsentiment.js @@ -0,0 +1,28 @@ + +module.exports = function(RED) { + "use strict"; + var multilangsentiment = require('multilang-sentiment'); + + function MultiLangSentimentNode(n) { + RED.nodes.createNode(this,n); + this.lang = n.lang; + this.property = n.property||"payload"; + var node = this; + + this.on("input", function(msg) { + var value = RED.util.getMessageProperty(msg,node.property); + if (value !== undefined) { + if (msg.hasOwnProperty("overrides")) { + msg.extras = msg.overrides; + delete msg.overrides; + } + multilangsentiment(value, node.lang || msg.lang || 'en', {words: msg.extras || null}, function (err, result) { + msg.sentiment = result; + node.send(msg); + }); + } + else { node.send(msg); } // If no matching property - just pass it on. + }); + } + RED.nodes.registerType("mlsentiment",MultiLangSentimentNode); +} diff --git a/analysis/mlsentiment/package.json b/analysis/mlsentiment/package.json new file mode 100644 index 00000000..4a2377fd --- /dev/null +++ b/analysis/mlsentiment/package.json @@ -0,0 +1,24 @@ +{ + "name" : "node-red-node-multilang-sentiment", + "version" : "0.1.0", + "description" : "A Node-RED node that uses the AFINN-165 wordlists for sentiment analysis of words translated into multiple languages including emojis.", + "dependencies" : { + "multilang-sentiment" : "^1.2.0" + }, + "repository" : { + "type":"git", + "url":"https://github.com/node-red/node-red-nodes/tree/master/analysis/sentiment" + }, + "license": "Apache-2.0", + "keywords": [ "node-red", "sentiment", "anaylsis", "AFINN" ], + "node-red" : { + "nodes" : { + "mlsentiment": "mlsentiment.js" + } + }, + "author": { + "name": "Dave Conway-Jones", + "email": "ceejay@vnet.ibm.com", + "url": "http://nodered.org" + } +} diff --git a/analysis/sentiment/72-sentiment.html b/analysis/sentiment/72-sentiment.html new file mode 100644 index 00000000..48778f46 --- /dev/null +++ b/analysis/sentiment/72-sentiment.html @@ -0,0 +1,36 @@ + + + diff --git a/analysis/sentiment/72-sentiment.js b/analysis/sentiment/72-sentiment.js new file mode 100644 index 00000000..29ec8671 --- /dev/null +++ b/analysis/sentiment/72-sentiment.js @@ -0,0 +1,22 @@ +module.exports = function(RED) { + "use strict"; + var sentiment = require('sentiment'); + + function SentimentNode(n) { + RED.nodes.createNode(this,n); + this.property = n.property||"payload"; + var node = this; + + this.on("input", function(msg) { + var value = RED.util.getMessageProperty(msg,node.property); + if (value !== undefined) { + sentiment(value, msg.overrides || null, function (err, result) { + msg.sentiment = result; + node.send(msg); + }); + } + else { node.send(msg); } // If no matching property - just pass it on. + }); + } + RED.nodes.registerType("sentiment",SentimentNode); +} diff --git a/analysis/sentiment/LICENSE b/analysis/sentiment/LICENSE new file mode 100644 index 00000000..786c01e8 --- /dev/null +++ b/analysis/sentiment/LICENSE @@ -0,0 +1,14 @@ +Copyright 2016, 2019 JS Foundation and other contributors, https://js.foundation/ +Copyright 2013-2016 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. diff --git a/analysis/sentiment/README.md b/analysis/sentiment/README.md new file mode 100644 index 00000000..e4bc449f --- /dev/null +++ b/analysis/sentiment/README.md @@ -0,0 +1,26 @@ +node-red-node-sentiment +======================== + +A Node-RED node that scores incoming words +using the AFINN-165 wordlist and attaches a sentiment.score property to the msg. + +NOTE: There is also a multi-language version available - **node-red-node-multilang-sentiment**. + +Install +------- + +This is a node that should be installed by default by Node-RED so you should not have to install it manually. If you do then run the following command in your Node-RED user directory - typically `~/.node-red` + + npm install node-red-node-sentiment + + +Usage +----- + +Uses the AFINN-165 wordlist to attempt to assign scores to words in text. + +Attaches `msg.sentiment` to the msg and within that `msg.sentiment.score` holds the score. + +A score greater than zero is positive and less than zero is negative. The score typically ranges from -5 to +5, but can go higher and lower. + +See the Sentiment docs here.
diff --git a/analysis/sentiment/locales/en-US/72-sentiment.html b/analysis/sentiment/locales/en-US/72-sentiment.html new file mode 100644 index 00000000..583d48bf --- /dev/null +++ b/analysis/sentiment/locales/en-US/72-sentiment.html @@ -0,0 +1,20 @@ + diff --git a/analysis/sentiment/locales/en-US/72-sentiment.json b/analysis/sentiment/locales/en-US/72-sentiment.json new file mode 100644 index 00000000..b65aad90 --- /dev/null +++ b/analysis/sentiment/locales/en-US/72-sentiment.json @@ -0,0 +1,8 @@ +{ + "sentiment": { + "sentiment": "sentiment", + "label": { + "language": "Language" + } + } +} diff --git a/analysis/sentiment/locales/ja/72-sentiment.html b/analysis/sentiment/locales/ja/72-sentiment.html new file mode 100644 index 00000000..bf8d95f0 --- /dev/null +++ b/analysis/sentiment/locales/ja/72-sentiment.html @@ -0,0 +1,35 @@ + + + diff --git a/analysis/sentiment/locales/ja/72-sentiment.json b/analysis/sentiment/locales/ja/72-sentiment.json new file mode 100644 index 00000000..6072d928 --- /dev/null +++ b/analysis/sentiment/locales/ja/72-sentiment.json @@ -0,0 +1,8 @@ +{ + "sentiment": { + "sentiment": "sentiment", + "label": { + "language": "言語" + } + } +} diff --git a/analysis/sentiment/package.json b/analysis/sentiment/package.json new file mode 100644 index 00000000..e432e8a9 --- /dev/null +++ b/analysis/sentiment/package.json @@ -0,0 +1,24 @@ +{ + "name" : "node-red-node-sentiment", + "version" : "0.1.6", + "description" : "A Node-RED node that uses the AFINN-165 wordlists for sentiment analysis of words.", + "dependencies" : { + "sentiment" : "2.1.0" + }, + "repository" : { + "type":"git", + "url":"https://github.com/node-red/node-red-nodes/tree/master/analysis/sentiment" + }, + "license": "Apache-2.0", + "keywords": [ "node-red", "sentiment", "anaylsis", "AFINN" ], + "node-red" : { + "nodes" : { + "sentiment": "72-sentiment.js" + } + }, + "author": { + "name": "Dave Conway-Jones", + "email": "ceejay@vnet.ibm.com", + "url": "http://nodered.org" + } +} diff --git a/analysis/swearfilter/74-swearfilter.js b/analysis/swearfilter/74-swearfilter.js index 8b68f8de..db29da6a 100644 --- a/analysis/swearfilter/74-swearfilter.js +++ b/analysis/swearfilter/74-swearfilter.js @@ -1,15 +1,14 @@ module.exports = function(RED) { "use strict"; - var badwords = require('badwords'); - if (badwords.length === 0 ) { return; } - var badwordsRegExp = require('badwords/regexp'); function BadwordsNode(n) { RED.nodes.createNode(this,n); + var badwordsRegExp = require('badwords/regexp'); var node = this; - this.on("input", function(msg) { + node.on("input", function(msg) { if (typeof msg.payload === "string") { + badwordsRegExp.lastIndex = 0 if ( !badwordsRegExp.test(msg.payload) ) { node.send(msg); } } }); diff --git a/analysis/swearfilter/package.json b/analysis/swearfilter/package.json index 076922df..cd08e1c5 100644 --- a/analysis/swearfilter/package.json +++ b/analysis/swearfilter/package.json @@ -1,9 +1,9 @@ { "name" : "node-red-node-badwords", - "version" : "0.0.5", + "version" : "0.1.0", "description" : "A Node-RED node that attempts to filter out messages containing swearwords.", "dependencies" : { - "badwords" : "0.0.3" + "badwords" : "^1.0.0" }, "repository" : { "type":"git", diff --git a/coverall b/coverall index c4a004f8..2d3f6e21 100755 --- a/coverall +++ b/coverall @@ -1,3 +1,3 @@ # check coverage of tests... and browse report -istanbul cover ./node_modules/.bin/grunt --report lcovonly && istanbul report html +nyc --check-coverage --reporter=html grunt /Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome coverage/index.html diff --git a/function/datagenerator/README.md b/function/datagenerator/README.md index 1e3beb40..617b4aa5 100644 --- a/function/datagenerator/README.md +++ b/function/datagenerator/README.md @@ -7,26 +7,26 @@ data values from a template. Useful for building test-cases. Install ------- -Run the following command in your Node-RED user directory - typically `~/.node-red` +Either use the Manage Palette option in the Node-RED Editor menu, or run the following command in your Node-RED user directory - typically `~/.node-red` - npm install node-red-node-data-generator + npm i node-red-node-data-generator Usage ----- Creates dummy data based on a handlebars-style template. -Uses the dummy-json +Uses the dummy-json module, which can create rich sets of dummy data for testing or other uses. It will build a **string**, or a **parsed JSON object**, creating values based on the helper names below: - title, firstname, lastname, company, domain, tld, email, + title, firstName, lastName, company, domain, tld, email, street, city, country, countryCode, zipcode, postcode, - lat, long, phone, color, hexColor, guid, - ipv4, ipv6, lorem, date, time, - lowercase, uppercase, int, float, boolean + lat, long, phone "+xx (x) xxxx xxx xxx", color, hexColor, guid, + ipv4, ipv6, lorem nn, date, time, + lowercase (helper), uppercase (helper), int, float, boolean Multiple values can be generated by use of the `repeat` syntax. diff --git a/function/datagenerator/datagenerator.html b/function/datagenerator/datagenerator.html index edfabacc..8ae22271 100644 --- a/function/datagenerator/datagenerator.html +++ b/function/datagenerator/datagenerator.html @@ -27,12 +27,12 @@ @@ -40,7 +40,6 @@ RED.nodes.registerType('data-generator',{ color:"rgb(243, 181, 103)", category: 'function', - paletteLabel:"data generator", defaults: { name: {value:""}, field: {value:"payload"}, @@ -51,9 +50,13 @@ inputs:1, outputs:1, icon: "template.png", + paletteLabel: function() { + return this._("datagen.datagen"); + }, label: function() { return this.name || "data generator"; }, + outputLabels: function() { return this.syntax === "json" ? "object" : "string"; }, oneditprepare: function() { var that = this; if (!this.fieldType) { diff --git a/function/datagenerator/locales/en-US/datagenerator.json b/function/datagenerator/locales/en-US/datagenerator.json index dffd35b3..a1f0ddcc 100644 --- a/function/datagenerator/locales/en-US/datagenerator.json +++ b/function/datagenerator/locales/en-US/datagenerator.json @@ -1,5 +1,6 @@ { "datagen": { + "datagen": "data generator", "label": { "syntax": "Return", "text": "a text string", diff --git a/function/datagenerator/package.json b/function/datagenerator/package.json index 5aae89e2..3dc05d16 100644 --- a/function/datagenerator/package.json +++ b/function/datagenerator/package.json @@ -1,9 +1,9 @@ { "name" : "node-red-node-data-generator", - "version" : "0.0.4", + "version" : "0.1.1", "description" : "A Node-RED node to create a string of dummy data values from a template. Useful for test-cases.", "dependencies" : { - "dummy-json": "1.0.*" + "dummy-json": "^2.0.0" }, "repository" : { "type":"git", diff --git a/function/random/LICENSE b/function/random/LICENSE index f5b60114..f144b137 100644 --- a/function/random/LICENSE +++ b/function/random/LICENSE @@ -1,4 +1,4 @@ -Copyright 2016 JS Foundation and other contributors, https://js.foundation/ +Copyright 2016,2020 JS Foundation and other contributors, https://js.foundation/ Copyright 2013-2016 IBM Corp. Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/function/random/README.md b/function/random/README.md index e27fdef7..91710a84 100644 --- a/function/random/README.md +++ b/function/random/README.md @@ -1,23 +1,24 @@ -node-red-node-random -==================== +# node-red-node-random A Node-RED node that when triggered generates a random number between two values. -Install -------- +## Install -Run the following command in your Node-RED user directory - typically `~/.node-red` +Either use the Manage Palette option in the Node-RED Editor menu, or run the following command in your Node-RED user directory - typically `~/.node-red` - npm install node-red-node-random + npm i node-red-node-random -Usage ------ +## Usage A simple node to generate a random number when triggered. -If integer mode is selected (default) it will return an integer **between and including** the two values given - so selecting 1 to 6 will return values 1,2,3,4,5 or 6. +If set to return an integer it can include both the low and high values. +`min <= n <= max` - so selecting 1 to 6 will return values 1,2,3,4,5 or 6. -If floating point mode is selected then it will return a number **between** the two values given - so selecting 1 to 6 will return values 1 < x < 6 . +If set to return a floating point value it will be from the low value, up to, but +**not** including the high value. `min <= n < max` - so selecting 1 to 6 will return values 1 <= n < 6 . -**Note:** This generates **numbers**. +You can dynamically pass in the 'From' and 'To' values to the node using msg.to and/or msg.from. **NOTE:** hard coded values in the node **always take precedence**. + +**Note:** This returns numbers - objects of type **number**. diff --git a/function/random/locales/en-US/random.html b/function/random/locales/en-US/random.html new file mode 100644 index 00000000..9c18b0da --- /dev/null +++ b/function/random/locales/en-US/random.html @@ -0,0 +1,16 @@ + diff --git a/function/random/locales/en-US/random.json b/function/random/locales/en-US/random.json new file mode 100644 index 00000000..e53fe58c --- /dev/null +++ b/function/random/locales/en-US/random.json @@ -0,0 +1,13 @@ +{ + "random": { + "label": { + "generate": "Generate", + "wholeNumber": "a whole number - integer", + "realNumber": "a real number - floating point", + "from": "From", + "lowestNumber": "lowest number", + "to": "To", + "highestNumber": "highest number" + } + } +} diff --git a/function/random/locales/ja/random.html b/function/random/locales/ja/random.html new file mode 100644 index 00000000..5b09fea8 --- /dev/null +++ b/function/random/locales/ja/random.html @@ -0,0 +1,16 @@ + diff --git a/function/random/locales/ja/random.json b/function/random/locales/ja/random.json new file mode 100644 index 00000000..fa3da7cb --- /dev/null +++ b/function/random/locales/ja/random.json @@ -0,0 +1,13 @@ +{ + "random": { + "label": { + "generate": "生成", + "wholeNumber": "整数 - 整数値", + "realNumber": "実数 - 浮動小数点", + "from": "最小", + "lowestNumber": "最小値", + "to": "最大", + "highestNumber": "最大値" + } + } +} diff --git a/function/random/package.json b/function/random/package.json index f6e48cac..2f2f4dc0 100644 --- a/function/random/package.json +++ b/function/random/package.json @@ -1,6 +1,6 @@ { "name" : "node-red-node-random", - "version" : "0.0.8", + "version" : "0.3.1", "description" : "A Node-RED node that when triggered generates a random number between two values.", "dependencies" : { }, @@ -19,5 +19,8 @@ "name": "Dave Conway-Jones", "email": "ceejay@vnet.ibm.com", "url": "http://nodered.org" - } + }, + "contributors": [ + {"name": "@zenofmud"} + ] } diff --git a/function/random/random.html b/function/random/random.html index 1a9cb7c7..7443615f 100644 --- a/function/random/random.html +++ b/function/random/random.html @@ -1,42 +1,40 @@ - - - - diff --git a/function/random/random.js b/function/random/random.js index 69da1d65..681cfefe 100644 --- a/function/random/random.js +++ b/function/random/random.js @@ -3,18 +3,73 @@ module.exports = function(RED) { "use strict"; function RandomNode(n) { RED.nodes.createNode(this,n); - this.low = Number(n.low || 1); - this.high = Number(n.high || 10); + + this.low = n.low + this.high = n.high this.inte = n.inte || false; + this.property = n.property||"payload"; var node = this; + var tmp = {}; + this.on("input", function(msg) { - if (node.inte == "true" || node.inte === true) { - msg.payload = Math.round(Number(Math.random()) * (node.high - node.low + 1) + node.low - 0.5); + + tmp.low = 1 // set this as the default low value + tmp.low_e = "" + if (node.low) { // if the the node has a value use it + tmp.low = Number(node.low); + } else if ('from' in msg) { // else see if a 'from' is in the msg + if (Number(msg.from)) { // if it is, and is a number, use it + tmp.low = Number(msg.from); + } else { // otherwise setup NaN error + tmp.low = NaN; + tmp.low_e = " From: " + msg.from; // setup to show bad incoming msg.from + } } - else { - msg.payload = Number(Math.random()) * (node.high - node.low) + node.low; + + tmp.high = 10 // set this as the default high value + tmp.high_e = ""; + if (node.high) { // if the the node has a value use it + tmp.high = Number(node.high); + } else if ('to' in msg) { // else see if a 'to' is in the msg + if (Number(msg.to)) { // if it is, and is a number, use it + tmp.high = Number(msg.to); + } else { // otherwise setup NaN error + tmp.high = NaN + tmp.high_e = " To: " + msg.to // setup to show bad incoming msg.to + } + } + + // if tmp.low or high are not numbers, send an error msg with bad values + if ( (isNaN(tmp.low)) || (isNaN(tmp.high)) ) { + this.error("Random: one of the input values is not a number. " + tmp.low_e + tmp.high_e); + } else { + // at this point we have valid values so now to generate the random number! + + // flip the values if low > high so random will work + var value = 0; + if (tmp.low > tmp.high) { + value = tmp.low + tmp.low = tmp.high + tmp.high = value + } + + // if returning an integer, do a math.ceil() on the low value and a + // Math.floor()high value before generate the random number. This must be + // done to insure the rounding doesn't round up if using something like 4.7 + // which would end up with 5 + if ( (node.inte == "true") || (node.inte === true) ) { + tmp.low = Math.ceil(tmp.low); + tmp.high = Math.floor(tmp.high); + // use this to round integers + value = Math.round(Math.random() * (tmp.high - tmp.low + 1) + tmp.low - 0.5); + } else { + // use this to round floats + value = (Math.random() * (tmp.high - tmp.low)) + tmp.low; + } + + RED.util.setMessageProperty(msg,node.property,value); + node.send(msg); } - node.send(msg); }); } RED.nodes.registerType("random",RandomNode); diff --git a/function/rbe/README.md b/function/rbe/README.md index 39ba78be..e63ae7ff 100644 --- a/function/rbe/README.md +++ b/function/rbe/README.md @@ -1,12 +1,11 @@ node-red-node-rbe ================= -A Node-RED node that provides +A Node-RED node that provides report-by-exception (RBE) and deadband capability. The node blocks unless the incoming value changes - RBE mode, or -changes by more than a certain amount (absolute value or percentage) - deadband -mode. +changes by more than a certain amount (absolute value or percentage) - bandgap modes. Install ------- @@ -19,7 +18,7 @@ Run the following command in your Node-RED user directory - typically `~/.node-r Usage ----- -A simple node to provide report by exception (RBE) and deadband function +A simple node to provide report by exception (RBE) and bandgap functions - only passes on data if it has changed. This works on a per `msg.topic` basis. This means that a single rbe node can @@ -31,12 +30,13 @@ The node doesn't send any output until the `msg.payload` is different to the pre Works on numbers and strings. Useful for filtering out repeated messages of the same value. Saves bandwidth, etc... -### Deadband modes +### Deadband and Narrowband modes -In deadband mode the incoming payload should contain a parseable *number* and is +In deadband modes the incoming payload should contain a parseable *number* and is output only if greater than + or - the *band gap* away from the previous output. -It can also be set to block values more than a certain distance away from the present value. -This can be used to remove outliers or unexpected readings. + +The narrowband modes will block if the incoming value change is greater than + or - the band gap +away from the previous value. Useful for ignoring outliers from a faulty sensor for example. You can specify compare with *previous valid output value* or *previous input value*. The former ignores any values outside the valid range, whereas the latter allows diff --git a/function/rbe/locales/en-US/rbe.html b/function/rbe/locales/en-US/rbe.html new file mode 100644 index 00000000..44d2068d --- /dev/null +++ b/function/rbe/locales/en-US/rbe.html @@ -0,0 +1,38 @@ + diff --git a/function/rbe/locales/en-US/rbe.json b/function/rbe/locales/en-US/rbe.json index d0d967bb..8adbb62a 100644 --- a/function/rbe/locales/en-US/rbe.json +++ b/function/rbe/locales/en-US/rbe.json @@ -1,5 +1,6 @@ { "rbe": { + "rbe": "rbe", "label": { "func": "Mode", "init": "Send initial value", diff --git a/function/rbe/locales/ja/rbe.html b/function/rbe/locales/ja/rbe.html new file mode 100644 index 00000000..cf2eea3e --- /dev/null +++ b/function/rbe/locales/ja/rbe.html @@ -0,0 +1,31 @@ + + + diff --git a/function/rbe/locales/ja/rbe.json b/function/rbe/locales/ja/rbe.json index fd913794..b4d76df0 100644 --- a/function/rbe/locales/ja/rbe.json +++ b/function/rbe/locales/ja/rbe.json @@ -13,12 +13,12 @@ "opts": { "rbe": "値が変化した時のみメッセージを中継", "rbei": "値が変化した時のみメッセージを中継(初期値を無視)", - "deadband": "値が比較値を超える時のみメッセージを中継", - "deadbandEq": "値が比較値以上の時のみメッセージを中継", - "narrowband": "初期値、値が比較値を超える時のみメッセージを中継", - "narrowbandEq": "初期値、値が比較値以上の時のみメッセージを中継", - "in": "を最後の入力値と比較", - "out": "を最後の出力値と比較" + "deadband": "値が指定した変化量を超える時のみメッセージを中継", + "deadbandEq": "値が指定した変化量以上の時のみメッセージを中継", + "narrowband": "値が指定した変化量を超えない時のみメッセージを中継", + "narrowbandEq": "値が指定した変化量以上でない時のみメッセージを中継", + "in": "最後の入力値と比較", + "out": "最後の出力値と比較" }, "warn": { "nonumber": "ペイロードに数値が含まれていません" diff --git a/function/rbe/package.json b/function/rbe/package.json index 6b1af48d..c66f41ee 100644 --- a/function/rbe/package.json +++ b/function/rbe/package.json @@ -1,7 +1,7 @@ { "name" : "node-red-node-rbe", - "version" : "0.1.14", - "description" : "A Node-RED node that provides report-by-exception (RBE) and deadband capability.", + "version" : "0.2.9", + "description" : "A Node-RED node that provides report-by-exception (RBE) and deadband capabilities.", "dependencies" : { }, "repository" : { diff --git a/function/rbe/rbe.html b/function/rbe/rbe.html index ecf951c2..70cad4c5 100644 --- a/function/rbe/rbe.html +++ b/function/rbe/rbe.html @@ -1,8 +1,8 @@ - - - - @@ -53,10 +61,12 @@ category: 'function', defaults: { name: {value:""}, + property: {value:"payload",required:true}, action: {value:"mean"}, count: {value:"10",required:true,validate:RED.validators.number()}, round: {value:""}, - mult: {value:"single"} + mult: {value:"single"}, + reduce: {value:false} }, inputs: 1, outputs: 1, @@ -67,7 +77,12 @@ labelStyle: function() { return this.name ? "node_label_italic" : ""; }, + outputLabels: function() { return this.reduce === true ? (this.action+" of "+this.count) : (this.action); }, oneditprepare: function() { + if (this.property === undefined) { + $("#node-input-property").val("payload"); + } + $("#node-input-property").typedInput({default:'msg',types:['msg']}); $("#node-input-count").spinner({ min:1 }); @@ -78,10 +93,12 @@ if ((a === "high") || ( a === "low" )) { $("#node-over").html("with a smoothing factor of "); $("#node-over2").html(""); + $("#row-input-reduce").hide(); } else { $("#node-over").html("over the most recent "); $("#node-over2").html(" values"); + $("#row-input-reduce").show(); } }); $("#node-input-action").change(); diff --git a/function/smooth/17-smooth.js b/function/smooth/17-smooth.js index c7fa914a..c34c04aa 100644 --- a/function/smooth/17-smooth.js +++ b/function/smooth/17-smooth.js @@ -9,11 +9,15 @@ module.exports = function(RED) { if (this.round == "true") { this.round = 0; } this.count = Number(n.count); this.mult = n.mult || "single"; + this.reduce = n.reduce || false; + this.property = n.property || "payload"; var node = this; var v = {}; this.on('input', function (msg) { + var value = RED.util.getMessageProperty(msg,node.property); var top = msg.topic || "_my_default_topic"; + var reduce = node.reduce; if (this.mult === "single") { top = "a"; } if ((v.hasOwnProperty(top) !== true) || msg.hasOwnProperty("reset")) { @@ -24,44 +28,51 @@ module.exports = function(RED) { v[top].pop = 0; v[top].old = null; v[top].count = this.count; + v[top].iter = 0; } - if (msg.hasOwnProperty("payload")) { - var n = Number(msg.payload); + if (value !== undefined) { + var n = Number(value); if (!isNaN(n)) { + v[top].iter++; if ((node.action === "low") || (node.action === "high")) { if (v[top].old == null) { v[top].old = n; } v[top].old = v[top].old + (n - v[top].old) / v[top].count; - if (node.action === "low") { msg.payload = v[top].old; } - else { msg.payload = n - v[top].old; } + if (node.action === "low") { value = v[top].old; } + else { value = n - v[top].old; } + reduce = false; } else { v[top].a.push(n); if (v[top].a.length > v[top].count) { v[top].pop = v[top].a.shift(); } if (node.action === "max") { - msg.payload = Math.max.apply(Math, v[top].a); + value = Math.max.apply(Math, v[top].a); } if (node.action === "min") { - msg.payload = Math.min.apply(Math, v[top].a); + value = Math.min.apply(Math, v[top].a); } if (node.action === "mean") { v[top].tot = v[top].tot + n - v[top].pop; - msg.payload = v[top].tot / v[top].a.length; + value = v[top].tot / v[top].a.length; } if (node.action === "sd") { v[top].tot = v[top].tot + n - v[top].pop; v[top].tot2 = v[top].tot2 + (n*n) - (v[top].pop * v[top].pop); if (v[top].a.length > 1) { - msg.payload = Math.sqrt((v[top].a.length * v[top].tot2 - v[top].tot * v[top].tot)/(v[top].a.length * (v[top].a.length - 1))); + value = Math.sqrt((v[top].a.length * v[top].tot2 - v[top].tot * v[top].tot)/(v[top].a.length * (v[top].a.length - 1))); } - else { msg.payload = 0; } + else { value = 0; } } } if (node.round !== false) { - msg.payload = Math.round(msg.payload * Math.pow(10, node.round)) / Math.pow(10, node.round); + value = Math.round(value * Math.pow(10, node.round)) / Math.pow(10, node.round); + } + if (reduce == false || v[top].iter == v[top].count) { + v[top].iter = 0; + RED.util.setMessageProperty(msg,node.property,value); + node.send(msg); } - node.send(msg); } - else { node.log("Not a number: "+msg.payload); } + else { node.log("Not a number: " + value); } } // ignore msg with no payload property. }); } diff --git a/function/smooth/package.json b/function/smooth/package.json index 349362fe..4dc0d67b 100644 --- a/function/smooth/package.json +++ b/function/smooth/package.json @@ -1,6 +1,6 @@ { "name" : "node-red-node-smooth", - "version" : "0.0.11", + "version" : "0.1.2", "description" : "A Node-RED node that provides several simple smoothing algorithms for incoming data values.", "dependencies" : { }, @@ -19,5 +19,6 @@ "name": "Dave Conway-Jones", "email": "ceejay@vnet.ibm.com", "url": "http://nodered.org" - } + }, + "contributors" : [ "@clickworkorange" ] } diff --git a/hardware/Arduino/35-arduino.html b/hardware/Arduino/35-arduino.html index d24ed1c0..b492fa28 100644 --- a/hardware/Arduino/35-arduino.html +++ b/hardware/Arduino/35-arduino.html @@ -1,13 +1,14 @@ - - - - - @@ -150,10 +152,7 @@ $("#node-config-lookup-serial-icon").addClass('fa-search'); $("#node-config-lookup-serial-icon").removeClass('spinner'); $("#node-config-lookup-serial").removeClass('disabled'); - var ports = []; - $.each(data, function(i, port) { - ports.push(port.comName); - }); + var ports = data || []; $("#node-config-input-device").autocomplete({ source:ports, minLength:0, diff --git a/hardware/Arduino/35-arduino.js b/hardware/Arduino/35-arduino.js index ca0a1d5c..8464312e 100644 --- a/hardware/Arduino/35-arduino.js +++ b/hardware/Arduino/35-arduino.js @@ -9,32 +9,46 @@ module.exports = function(RED) { function ArduinoNode(n) { RED.nodes.createNode(this,n); this.device = n.device || null; + this.running = false; + this.reported = false; var node = this; - node.board = Board(node.device, function(e) { - //console.log("ERR",e); - if ((e !== undefined) && (e.toString().indexOf("cannot open") !== -1) ) { - node.error(RED._("arduino.errors.portnotfound",{device:node.device})); - } - else if (e === undefined) { - node.board.on('ready', function() { - node.log(RED._("arduino.status.connected",{device:node.board.sp.path})); - if (RED.settings.verbose) { - node.log(RED._("arduino.status.version",{version:node.board.firmware.name+"-"+node.board.version.major+"."+node.board.version.minor})); + var startup = function() { + node.board = new Board(node.device, function(e) { + if ((e !== undefined) && (e.toString().indexOf("cannot open") !== -1) ) { + if (node.reported === false) { + node.error(RED._("arduino.errors.portnotfound",{device:node.device})); + node.reported = true; } - }); - } - node.board.on('close', function() { - node.error(RED._("arduino.status.portclosed")); + } + else if (e === undefined) { + node.running = true; + node.reported = false; + node.board.once('ready', function() { + node.log(RED._("arduino.status.connected",{device:node.board.sp.path})); + if (RED.settings.verbose) { + node.log(RED._("arduino.status.version",{version:node.board.firmware.name+"-"+node.board.version.major+"."+node.board.version.minor})); + } + }); + node.board.once('close', function() { + node.error(RED._("arduino.status.portclosed")); + }); + node.board.once('disconnect', function() { + if (node.running === true) { setTimeout(function() { node.running = false; startup(); }, 5000); } + }); + } }); - }); + setTimeout(function() { if (node.running === false) { startup(); } }, 5000); + }; + startup(); node.on('close', function(done) { + node.running = false; if (node.board) { try { - node.board.sp.close(function() { - done(); + node.board.transport.close(function() { if (RED.settings.verbose) { node.log(RED._("arduino.status.portclosed")); } + done(); }); } catch(e) { done(); } @@ -53,43 +67,69 @@ module.exports = function(RED) { this.state = n.state; this.arduino = n.arduino; this.serverConfig = RED.nodes.getNode(this.arduino); + this.running = false; + var node = this; if (typeof this.serverConfig === "object") { - this.board = this.serverConfig.board; - var node = this; - node.status({fill:"red",shape:"ring",text:"node-red:common.status.connecting"}); - var doit = function() { - node.status({fill:"green",shape:"dot",text:"node-red:common.status.connected"}); - if (node.state === "ANALOG") { - node.board.pinMode(node.pin, 0x02); - node.board.analogRead(node.pin, function(v) { - node.send({payload:v, topic:"A"+node.pin}); + var startup = function() { + node.board = node.serverConfig.board; + node.board.setMaxListeners(0); + node.oldval = ""; + node.status({fill:"grey",shape:"ring",text:"node-red:common.status.connecting"}); + var doit = function() { + node.running = true; + if (node.state === "ANALOG") { node.board.pinMode(node.pin, 0x02); } + if (node.state === "INPUT") { node.board.pinMode(node.pin, 0x00); } + if (node.state === "PULLUP") { node.board.pinMode(node.pin, 0x0B); } + node.status({fill:"green",shape:"dot",text:"node-red:common.status.connected"}); + if (node.state === "ANALOG") { + node.board.analogRead(node.pin, function(v) { + if (v !== node.oldval) { + node.oldval = v; + node.send({payload:v, topic:"A"+node.pin}); + } + }); + } + if (node.state === "INPUT") { + node.board.digitalRead(node.pin, function(v) { + if (v !== node.oldval) { + node.oldval = v; + node.send({payload:v, topic:node.pin}); + } + }); + } + if (node.state === "PULLUP") { + node.board.digitalRead(node.pin, function(v) { + if (v !== node.oldval) { + node.oldval = v; + node.send({payload:v, topic:node.pin}); + } + }); + } + if (node.state == "STRING") { + node.board.on('string', function(v) { + if (v !== node.oldval) { + node.oldval = v; + node.send({payload:v, topic:"string"}); + } + }); + } + node.board.once('disconnect', function() { + node.status({fill:"red",shape:"ring",text:"node-red:common.status.not-connected"}); + if (node.running) { setTimeout(function() { node.running = false; startup(); }, 5500); } }); } - if (node.state === "INPUT") { - node.board.pinMode(node.pin, 0x00); - node.board.digitalRead(node.pin, function(v) { - node.send({payload:v, topic:node.pin}); - }); - } - if (node.state == "STRING") { - node.board.on('string', function(v) { - node.send({payload:v, topic:"string"}); - }); - } - // node.board.on('close', function() { - // node.board.removeAllListeners(); - // node.status({fill:"grey",shape:"ring",text:"node-red:common.status.not-connected"}); - // }); + if (node.board.isReady) { doit(); } + else { node.board.once("ready", function() { doit(); }); } + setTimeout(function() { if (node.running === false) { startup(); } }, 4500); } - if (node.board.isReady) { doit(); } - else { node.board.on("ready", function() { doit(); }); } - node.on("close", function() { - if (node.tout) { clearTimeout(node.tout); } - }) + startup(); } else { - this.warn(RED._("arduino.errors.portnotconf")); + node.warn(RED._("arduino.errors.portnotconf")); } + node.on('close', function() { + node.running = false; + }); } RED.nodes.registerType("arduino in",DuinoNodeIn); @@ -102,63 +142,78 @@ module.exports = function(RED) { this.state = n.state; this.arduino = n.arduino; this.serverConfig = RED.nodes.getNode(this.arduino); - if (typeof this.serverConfig === "object") { - this.board = this.serverConfig.board; - var node = this; - node.status({fill:"red",shape:"ring",text:"node-red:common.status.connecting"}); - var doit = function() { - node.status({fill:"green",shape:"dot",text:"node-red:common.status.connected"}); - node.on("input", function(msg) { - if (node.state === "OUTPUT") { - node.board.pinMode(node.pin, 0x01); - if ((msg.payload === true)||(msg.payload.toString() == "1")||(msg.payload.toString().toLowerCase() == "on")) { - node.board.digitalWrite(node.pin, node.board.HIGH); + this.running = false; + var node = this; + if (typeof node.serverConfig === "object") { + var startup = function() { + node.board = node.serverConfig.board; + node.board.setMaxListeners(0); + node.status({fill:"grey",shape:"ring",text:"node-red:common.status.connecting"}); + var doit = function() { + node.running = true; + if (node.state === "OUTPUT") { node.board.pinMode(node.pin, 0x01); } + if (node.state === "PWM") { node.board.pinMode(node.pin, 0x03); } + if (node.state === "SERVO") { node.board.pinMode(node.pin, 0x04); } + node.status({fill:"green",shape:"dot",text:"node-red:common.status.connected"}); + node.on("input", function(msg) { + if (node.board.isReady) { + if (node.state === "OUTPUT") { + if ((msg.payload === true)||(msg.payload.toString() == "1")||(msg.payload.toString().toLowerCase() == "on")) { + node.board.digitalWrite(node.pin, node.board.HIGH); + } + if ((msg.payload === false)||(msg.payload.toString() == "0")||(msg.payload.toString().toLowerCase() == "off")) { + node.board.digitalWrite(node.pin, node.board.LOW); + } + } + if (node.state === "PWM") { + msg.payload = parseInt((msg.payload * 1) + 0.5); + if ((msg.payload >= 0) && (msg.payload <= 255)) { + node.board.analogWrite(node.pin, msg.payload); + } + } + if (node.state === "SERVO") { + msg.payload = parseInt((msg.payload * 1) + 0.5); + if ((msg.payload >= 0) && (msg.payload <= 180)) { + node.board.servoWrite(node.pin, msg.payload); + } + } + if (node.state === "SYSEX") { + node.board.sysexCommand(msg.payload); + } + if (node.state === "STRING") { + node.board.sendString(msg.payload.toString()); + } } - if ((msg.payload === false)||(msg.payload.toString() == "0")||(msg.payload.toString().toLowerCase() == "off")) { - node.board.digitalWrite(node.pin, node.board.LOW); - } - } - if (node.state === "PWM") { - node.board.pinMode(node.pin, 0x03); - msg.payload = parseInt((msg.payload * 1) + 0.5); - if ((msg.payload >= 0) && (msg.payload <= 255)) { - node.board.analogWrite(node.pin, msg.payload); - } - } - if (node.state === "SERVO") { - node.board.pinMode(node.pin, 0x04); - msg.payload = parseInt((msg.payload * 1) + 0.5); - if ((msg.payload >= 0) && (msg.payload <= 180)) { - node.board.servoWrite(node.pin, msg.payload); - } - } - if (node.state === "SYSEX") { - node.board.sysexCommand(msg.payload); - } - if (node.state === "STRING") { - node.board.sendString(msg.payload.toString()); - } - }); - // node.board.on('close', function() { - // node.status({fill:"grey",shape:"ring",text:"node-red:common.status.not-connected"}); - // }); + }); + node.board.once('disconnect', function() { + node.status({fill:"red",shape:"ring",text:"node-red:common.status.not-connected"}); + if (node.running === true) { setTimeout(function() { node.running = false; startup(); }, 5500); } + }); + } + if (node.board.isReady) { doit(); } + else { node.board.once("ready", function() { doit(); }); } + setTimeout(function() { if (node.running === false) { startup(); } }, 4500); } - if (node.board.isReady) { doit(); } - else { node.board.on("ready", function() { doit(); }); } - node.on("close", function() { - if (node.tout) { clearTimeout(node.tout); } - }) + startup(); } else { - this.warn(RED._("arduino.errors.portnotconf")); + node.warn(RED._("arduino.errors.portnotconf")); } + node.on('close', function() { + node.running = false; + }); } RED.nodes.registerType("arduino out",DuinoNodeOut); RED.httpAdmin.get("/arduinoports", RED.auth.needsPermission("arduino.read"), function(req,res) { - SP.list(function(error, ports) { - res.json(ports); - }); - + SP.list().then( + ports => { + const a = ports.map(p => p.comName); + res.json(a); + }, + err => { + this.log('Error listing serial ports', err) + } + ) }); } diff --git a/hardware/Arduino/README.md b/hardware/Arduino/README.md index 9bce8882..ee3f08d4 100644 --- a/hardware/Arduino/README.md +++ b/hardware/Arduino/README.md @@ -7,7 +7,7 @@ Arduino running standard firmata 2.2 or better. Install ------- -Run the following command in your Node-RED user directory - typically `~/.node-red` +Either use the Menu - Manage Palette option or run the following command in your Node-RED user directory - typically `~/.node-red` npm i --unsafe-perm node-red-node-arduino @@ -23,12 +23,10 @@ details and examples of how to use this node. Connects to local Arduino and monitors the selected pin for changes. -You can select either **Digital**, **Analogue**, or **String** input type. +You can select either **Digital**, **Pullup**, **Analogue**, or **String** input type. Outputs the value read as `msg.payload` and the pin number as `msg.topic`. -It only outputs on a change of value - fine for digital inputs, but you can get a lot of data from analogue pins which you must then handle. - -You can set the sample rate from `20` to `65535` mS. +It only outputs on a change of value - fine for digital inputs, but you can get a lot of data from analogue pins which you must then handle. For example you could use a `delay` node set to rate limit and drop intermediate values, or an `rbe` node to only report when it changes by a certain amount. ### Output Node diff --git a/hardware/Arduino/locales/en-US/35-arduino.json b/hardware/Arduino/locales/en-US/35-arduino.json index 7e731a50..640bfdc1 100644 --- a/hardware/Arduino/locales/en-US/35-arduino.json +++ b/hardware/Arduino/locales/en-US/35-arduino.json @@ -18,6 +18,7 @@ "state": { "in": { "digital": "Digital pin", + "pullup": "Digital pin with pullup", "analogue": "Analogue pin", "string": "String" }, diff --git a/hardware/Arduino/package.json b/hardware/Arduino/package.json index 53aa3d7d..14a9d18f 100644 --- a/hardware/Arduino/package.json +++ b/hardware/Arduino/package.json @@ -1,9 +1,9 @@ { "name" : "node-red-node-arduino", - "version" : "0.0.14", + "version" : "0.3.1", "description" : "A Node-RED node to talk to an Arduino running firmata", "dependencies" : { - "firmata" : "~0.17.0" + "firmata" : "^2.3.0" }, "repository" : { "type":"git", @@ -16,6 +16,7 @@ "arduino": "35-arduino.js" } }, + "engines" : { "node" : ">=8" }, "author": { "name": "Dave Conway-Jones", "email": "ceejay@vnet.ibm.com", diff --git a/hardware/LEDborg/78-ledborg.js b/hardware/LEDborg/78-ledborg.js index dea2ebe9..e9aee071 100644 --- a/hardware/LEDborg/78-ledborg.js +++ b/hardware/LEDborg/78-ledborg.js @@ -8,23 +8,26 @@ module.exports = function(RED) { var LedBorgInUse = false; var gpioCommand = __dirname+'/nrgpio'; + var allOK = true; try { var cpuinfo = fs.readFileSync("/proc/cpuinfo").toString(); - if (cpuinfo.indexOf(": BCM") === -1) { throw "Info : "+RED._("rpi-gpio.errors.ignorenode"); } + if (cpuinfo.indexOf(": BCM") === -1) { + RED.log.warn("ledborg : "+RED._("node-red:rpi-gpio.errors.ignorenode")); + allOK = false; + } + else if (!fs.existsSync("/usr/share/doc/python-rpi.gpio")) { + RED.log.warn("ledborg : "+RED._("node-red:rpi-gpio.errors.libnotfound")); + allOK = false; + } + else if ( !(1 & parseInt ((fs.statSync(gpioCommand).mode & parseInt ("777", 8)).toString (8)[0]) )) { + RED.log.warn("ledborg : "+RED._("node-red:rpi-gpio.errors.needtobeexecutable",{command:gpioCommand})); + allOK = false; + } } catch(err) { - throw "Info : "+RED._("rpi-gpio.errors.ignorenode"); - } - - if (!fs.existsSync("/usr/share/doc/python-rpi.gpio")) { - util.log("[rpi-ledborg] Info : Can't find RPi.GPIO python library."); - throw "Warning : Can't find RPi.GPIO python library."; - } - - if ( !(1 & parseInt ((fs.statSync(gpioCommand).mode & parseInt ("777", 8)).toString (8)[0]) )) { - util.log("[rpi-ledborg] Error : "+gpioCommand+" needs to be executable."); - throw "Error : nrgpio must to be executable."; + RED.log.warn("ledborg : "+RED._("node-red:rpi-gpio.errors.ignorenode")); + allOK = false; } // GPIO pins 11 (R), 13 (G), 15 (B). @@ -95,46 +98,50 @@ module.exports = function(RED) { } } - node.child = spawn(gpioCommand, ["borg","11"]); - node.running = true; - node.status({fill:"green",shape:"dot",text:"OK"}); + if (allOK === true) { + node.child = spawn(gpioCommand, ["borg","11"]); + node.running = true; + node.status({fill:"green",shape:"dot",text:"OK"}); - node.on("input", inputlistener); + node.on("input", inputlistener); - node.child.stdout.on('data', function (data) { - if (RED.settings.verbose) { node.log("out: "+data+" :"); } - }); + 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.stderr.on('data', function (data) { + if (RED.settings.verbose) { node.log("err: "+data+" :"); } + }); - node.child.on('close', function () { - node.child = null; - node.running = false; - node.status({fill:"red",shape:"circle",text:""}); - if (RED.settings.verbose) { node.log("closed"); } - if (node.done) { node.done(); } - }); + node.child.on('close', function () { + node.child = null; + node.running = false; + node.status({fill:"red",shape:"circle",text:""}); + if (RED.settings.verbose) { node.log("closed"); } + if (node.finished) { node.finished(); } + }); - 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); } - }); + 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); } + }); - node.on("close", function(done) { + node.on("close", function(done) { + LedBorgInUse = false; + node.status({fill:"red",shape:"circle",text:""}); + if (node.child != null) { + node.fisnished = done; + node.child.stdin.write(" close 11"); + node.child.kill('SIGKILL'); + } + else { done(); } + }); + } + else { LedBorgInUse = false; - node.status({fill:"red",shape:"circle",text:""}); - if (node.child != null) { - node.done = done; - node.child.stdin.write(" close 11"); - node.child.kill('SIGKILL'); - } - else { done(); } - - }); - + node.status({fill:"grey",shape:"dot",text:"node-red:rpi-gpio.status.not-available"}); + } } RED.nodes.registerType("ledborg",LedBorgNode); } diff --git a/hardware/LEDborg/nrgpio.py b/hardware/LEDborg/nrgpio.py index da5213af..e284d1e6 100755 --- a/hardware/LEDborg/nrgpio.py +++ b/hardware/LEDborg/nrgpio.py @@ -4,11 +4,12 @@ import RPi.GPIO as GPIO import sys -bounce = 20 # bounce time in mS to apply +try: + raw_input # Python 2 +except NameError: + raw_input = input # Python 3 -if sys.version_info >= (3,0): - print("Sorry - currently only configured to work with python 2.x") - sys.exit(1) +bounce = 20 # bounce time in mS to apply if len(sys.argv) > 1: cmd = sys.argv[1].lower() @@ -17,7 +18,7 @@ if len(sys.argv) > 1: GPIO.setwarnings(False) if cmd == "pwm": - #print "Initialised pin "+str(pin)+" to PWM" + #print("Initialised pin "+str(pin)+" to PWM") GPIO.setup(pin,GPIO.OUT) p = GPIO.PWM(pin, 100) p.start(0) @@ -32,10 +33,10 @@ if len(sys.argv) > 1: GPIO.cleanup(pin) sys.exit(0) except Exception as ex: - print "bad data: "+data + print("bad data: "+data) elif cmd == "buzz": - #print "Initialised pin "+str(pin)+" to Buzz" + #print("Initialised pin "+str(pin)+" to Buzz") GPIO.setup(pin,GPIO.OUT) p = GPIO.PWM(pin, 100) p.stop() @@ -54,10 +55,10 @@ if len(sys.argv) > 1: GPIO.cleanup(pin) sys.exit(0) except Exception as ex: - print "bad data: "+data + print("bad data: "+data) elif cmd == "out": - #print "Initialised pin "+str(pin)+" to 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])) @@ -78,9 +79,9 @@ if len(sys.argv) > 1: GPIO.output(pin,data) elif cmd == "in": - #print "Initialised pin "+str(pin)+" to IN" + #print("Initialised pin "+str(pin)+" to IN") def handle_callback(chan): - print GPIO.input(chan) + print(GPIO.input(chan)) if len(sys.argv) == 4: if sys.argv[3].lower() == "up": @@ -91,7 +92,7 @@ if len(sys.argv) > 1: GPIO.setup(pin,GPIO.IN) else: GPIO.setup(pin,GPIO.IN) - print GPIO.input(pin) + print(GPIO.input(pin)) GPIO.add_event_detect(pin, GPIO.BOTH, callback=handle_callback, bouncetime=bounce) while True: @@ -104,7 +105,7 @@ if len(sys.argv) > 1: sys.exit(0) elif cmd == "byte": - #print "Initialised BYTE mode - "+str(pin)+ + #print("Initialised BYTE mode - "+str(pin)+) list = [7,11,13,12,15,16,18,22] GPIO.setup(list,GPIO.OUT) @@ -127,7 +128,7 @@ if len(sys.argv) > 1: GPIO.output(list[bit], data & mask) elif cmd == "borg": - #print "Initialised BORG mode - "+str(pin)+ + #print("Initialised BORG mode - "+str(pin)+) GPIO.setup(11,GPIO.OUT) GPIO.setup(13,GPIO.OUT) GPIO.setup(15,GPIO.OUT) @@ -154,10 +155,10 @@ if len(sys.argv) > 1: data = 0 elif cmd == "rev": - print GPIO.RPI_REVISION + print(GPIO.RPI_REVISION) elif cmd == "ver": - print GPIO.VERSION + print(GPIO.VERSION) elif cmd == "mouse": # catch mice button events file = open( "/dev/input/mice", "rb" ) @@ -171,7 +172,7 @@ if len(sys.argv) > 1: button = ord( buf[0] ) & pin # mask out just the required button(s) if button != oldbutt: # only send if changed oldbutt = button - print button + print(button) while True: try: @@ -181,4 +182,4 @@ if len(sys.argv) > 1: sys.exit(0) else: - print "Bad parameters - in|out|pwm|buzz|byte|borg|mouse|ver pin {value|up|down}" + print("Bad parameters - in|out|pwm|buzz|byte|borg|mouse|ver pin {value|up|down}") diff --git a/hardware/LEDborg/package.json b/hardware/LEDborg/package.json index 787b388a..9d5dbdb7 100644 --- a/hardware/LEDborg/package.json +++ b/hardware/LEDborg/package.json @@ -1,6 +1,6 @@ { "name" : "node-red-node-ledborg", - "version" : "0.0.18", + "version" : "0.0.22", "description" : "A Node-RED node to control a PiBorg LedBorg board for a Raspberry Pi.", "dependencies" : { }, diff --git a/hardware/PiGpio/36-rpi-gpio.html b/hardware/PiGpio/36-rpi-gpio.html new file mode 100644 index 00000000..a4a66deb --- /dev/null +++ b/hardware/PiGpio/36-rpi-gpio.html @@ -0,0 +1,610 @@ + + + + + + + + + + + + + + + + diff --git a/hardware/PiGpio/36-rpi-gpio.js b/hardware/PiGpio/36-rpi-gpio.js new file mode 100644 index 00000000..1c263f30 --- /dev/null +++ b/hardware/PiGpio/36-rpi-gpio.js @@ -0,0 +1,359 @@ + +module.exports = function(RED) { + "use strict"; + var execSync = require('child_process').execSync; + var exec = require('child_process').exec; + var spawn = require('child_process').spawn; + + var testCommand = __dirname+'/testgpio.py' + var gpioCommand = __dirname+'/nrgpio'; + var allOK = true; + + try { + execSync(testCommand); + } 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:"yellow",shape:"dot",text:"rpi-gpio.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.finished) { + node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"}); + node.finished(); + } + 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:"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.finished = 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, send, done) { + 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", () => { + if (done) { done(); } + }); + 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:"yellow",shape:"dot",text:"rpi-gpio.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.finished) { + node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"}); + node.finished(); + } + 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:"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.finished = 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:"rpi-gpio.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.finished) { + node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"}); + node.finished(); + } + 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.finished = done; + node.child.kill('SIGINT'); + node.child = null; + } + else { done(); } + }); + } + else { + node.status({fill:"grey",shape:"dot",text:"rpi-gpio.status.not-available"}); + } + } + RED.nodes.registerType("rpi-mouse",PiMouseNode); + + function PiKeyboardNode(n) { + RED.nodes.createNode(this,n); + var node = this; + + var doConnect = function() { + node.child = spawn(gpioCommand+".py", ["kbd","0"]); + node.status({fill:"green",shape:"dot",text:"rpi-gpio.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] !== '') { + var b = d[i].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.finished) { + node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"}); + node.finished(); + } + else { + node.status({fill:"red",shape:"ring",text:"rpi-gpio.status.stopped"}); + setTimeout(function() { doConnect(); },2000) + } + }); + + 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); } + }); + } + + if (allOK === true) { + doConnect(); + + node.on("close", function(done) { + node.status({}); + if (node.child != null) { + node.finished = done; + node.child.kill('SIGINT'); + node.child = null; + } + else { done(); } + }); + } + else { + node.status({fill:"grey",shape:"dot",text:"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/README.md b/hardware/PiGpio/README.md new file mode 100644 index 00000000..e4b548b2 --- /dev/null +++ b/hardware/PiGpio/README.md @@ -0,0 +1,63 @@ +node-red-node-pi-gpio +===================== + +A set of Node-RED nodes to interact with Pi GPIO using the RPi.GPIO python library that is part of Raspbian. + +It also include a simple node that detect mouse buttons and also keyboard clicks. Note: this +picks up mouse keys direct from the keyboard so should work even when the app does not have +focus, but YMMV. + +If you need servo control then look at the +node-red-node-pi-gpiod node +as this is a lot more accurate timing wise, and more suitable for driving servos + +## Install + +Either use the Node-RED Menu - Manage Palette option to install, or run the following +command in your Node-RED user directory - typically `~/.node-red` + + npm i node-red-node-pi-gpio + +The python library may also work with other distros running on a Pi (like Ubuntu or Debian) - you will need to install the PIGPIO package and run the following commands in order to gain full access to the GPIO pins as this ability is not part of the default distro. This is NOT necessary on Raspbian. + + sudo apt-get install python-pip python-dev + sudo pip install RPi.GPIO + sudo addgroup gpio + sudo chown root:gpio /dev/gpiomem + sudo adduser $USER gpio + echo 'KERNEL=="gpiomem", NAME="%k", GROUP="gpio", MODE="0660"' | sudo tee /etc/udev/rules.d/45-gpio.rules + sudo udevadm control --reload-rules && sudo udevadm trigger + +## Usage + +**Note:** the pin numbers refer the physical pin numbers on connector P1 as they are easier to locate. + +### Input node + +Generates a `msg.payload` with either a 0 or 1 depending on the state of the input pin. + +##### Outputs + + - `msg.payload` - *number* - the level of the pin (0 or 1) + - `msg.topic` - *string* - pi/{the pin number} + +You may also enable the input pullup resistor ↑ or the pulldown resistor ↓. + +### Output node + +Can be used in Digital or PWM modes. + +##### Input + + - `msg.payload` - *number | string* + - Digital - 0, 1 - set pin low or high. (Can also accept boolean `true/false`) + - PWM - 0 to 100 - level from 0 to 100% + +*Hint*: The `range` node can be used to scale inputs to the correct values. + +Digital mode expects a `msg.payload` with either a 0 or 1 (or true or false), +and will set the selected physical pin high or low depending on the value passed in. + +The initial value of the pin at deploy time can also be set to 0 or 1. + +When using PWM mode, the input value should be a number 0 - 100, and can be floating point. 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..30164ebc --- /dev/null +++ b/hardware/PiGpio/locales/de/36-rpi-gpio.html @@ -0,0 +1,74 @@ + + + + + + + + + diff --git a/hardware/PiGpio/locales/de/36-rpi-gpio.json b/hardware/PiGpio/locales/de/36-rpi-gpio.json new file mode 100644 index 00000000..e1d44a8b --- /dev/null +++ b/hardware/PiGpio/locales/de/36-rpi-gpio.json @@ -0,0 +1,73 @@ +"rpi-gpio" : { + "label" : { + "gpiopin" : "GPIO", + "selectpin" : "Auswahlstift", + "resistor" : "Widerstand?", + "readinitial" : "Anfangsstatus des Pins bei Implementierung/Neustart lesen?", + "type" : "Typ", + "initpin" : "Pin-Status initialisieren?", + "debounce" : "Debounce", + "freq" : "Frequenz", + "button" : "Knopf", + "pimouse" : "Pi-Maus", + "pikeyboard" : "Pi-Tastatur", + "left" : "Links", + "right" : "Rechts", + "middle" : "Mitte" + }, + "resistor" : { + "none" : "keine", + "pullup" : "pullup", + "pulldown" : "Pulldown" + }, + "digout" : "Digitale Ausgabe", + "pwmout" : "PWM-Ausgabe", + "servo" : "Servo-Ausgabe", + "initpin0" : "Anfangsstand des Pin-Niedrig (0)", + "initpin1" : "Anfangsebene von Pin-High (1)", + "left" : "links", + "right" : "rechts", + "middle" : "Mitte", + "any" : "beliebig", + "pinname" : "Pin", + "alreadyuse" : "bereits im Gebrauch", + "alreadyset" : "bereits festgelegt als", + "tip" : { + "pin" : " Verwender Pins : ", + "in" : "Tipp: Es wird nur die digitale Eingabe unterstützt. Die Eingabe muss 0 oder 1 sein.", + "dig" : "Tipp: Für die digitale Ausgabe muss der Wert 0 oder 1 sein.", + "pwm" : "Tipp: Für die PWM-Ausgabe muss der Wert zwischen 0 und 100 liegen; die Einstellung der Hochfrequenz kann mehr CPU beanspruchen als erwartet.", + "ser" : " Tipp : Für die Servo-Ausgabe muss ein Wert zwischen 0 und 100 eingegeben werden. 50 ist das Zentrum." + }, + "types" : { + "digout" : "digitale Ausgabe", + "input" : "Eingabe", + "pullup" : "Eingabe mit Pull-up", + "pulldown" : "Eingabe mit Pull-down", + "pwmout" : "PWM-Ausgabe", + "servo" : "Servo-Ausgabe" + }, + "status" : { + "stopped" : "Gestoppt", + "closed" : "geschlossen", + "not-running" : "nicht aktiv", + "not-available" : "nicht verfügbar", + "na" : "N/A: __Wert__", + "ok": "OK" + }, + "errors" : { + "ignorenode" : "Raspberry Pi-spezifische Nodes inaktiv", + "version" : "Abrufen der Version von Pi fehlgeschlagen", + "sawpitype" : "Saw-Pi-Typ", + "libnotfound" : "Pi RPi.GPIO-Python-Bibliothek nicht gefunden", + "alreadyset" : "GPIO-Stift __pin__ ist bereits als Typ festgelegt: __type__", + "invalidpin" : "Ungültiger GPIO-Pin", + "invalidinput" : "Ungültige Eingabe", + "needtobeexecutable" : "__command__ muss ausführbar sein", + "mustbeexecutable" : "nrgpio muss ausführbar sein", + "commandnotfound" : "Befehl 'nrgpio' nicht gefunden", + "commandnotexecutable" : "nrgpio-Befehl nicht ausführbar", + "error" : "Fehler: __error__", + "pythoncommandnotfound" : "Befehl 'nrgpio python' nicht aktiv" + } + } diff --git a/hardware/PiGpio/locales/en-US/36-rpi-gpio.html b/hardware/PiGpio/locales/en-US/36-rpi-gpio.html new file mode 100644 index 00000000..5659c686 --- /dev/null +++ b/hardware/PiGpio/locales/en-US/36-rpi-gpio.html @@ -0,0 +1,75 @@ + + + + + + + + + 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..8931f170 --- /dev/null +++ b/hardware/PiGpio/locales/en-US/36-rpi-gpio.json @@ -0,0 +1,75 @@ +{ + "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__", + "ok": "OK" + }, + "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": "nrgpio 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..bdf8af2d --- /dev/null +++ b/hardware/PiGpio/locales/ja/36-rpi-gpio.html @@ -0,0 +1,71 @@ + + + + + + + + + diff --git a/hardware/PiGpio/locales/ja/36-rpi-gpio.json b/hardware/PiGpio/locales/ja/36-rpi-gpio.json new file mode 100644 index 00000000..619eb3ef --- /dev/null +++ b/hardware/PiGpio/locales/ja/36-rpi-gpio.json @@ -0,0 +1,75 @@ +{ + "rpi-gpio": { + "label": { + "gpiopin": "GPIO", + "selectpin": "端子の選択", + "resistor": "抵抗", + "readinitial": "デプロイや再起動時に端子の初期状態を読み込む", + "type": "出力形式", + "initpin": "端子の状態を初期化", + "debounce": "デバウンス", + "freq": "頻度", + "button": "ボタン", + "pimouse": "Pi Mouse", + "pikeyboard": "Pi Keyboard", + "left": "Left", + "right": "Right", + "middle": "Middle" + }, + "resistor": { + "none": "なし", + "pullup": "プルアップ", + "pulldown": "プルダウン" + }, + "digout": "デジタル出力", + "pwmout": "PWM出力", + "servo": "サーボ出力", + "initpin0": "端子の初期レベル - Low (0)", + "initpin1": "端子の初期レベル - High (1)", + "left": "左", + "right": "右", + "middle": "中間", + "any": "全て", + "pinname": "端子", + "alreadyuse": "使用中", + "alreadyset": "設定済", + "tip": { + "pin": "使用中の端子: ", + "in": "注釈: 入力値は、0または1の数値のみ対応しています。", + "dig": "注釈: 「出力形式」として「デジタル出力」を用いる場合、入力値は0または1の数値である必要があります。", + "pwm": "注釈: 「出力形式」として「PWM出力」を用いる場合、入力値は0~100の数値である必要があります。", + "ser": "注釈: サーボ出力向け - 入力値は0~100の間である必要があります。50が中心値です。" + }, + "types": { + "digout": "デジタル出力", + "input": "入力", + "pullup": "プルアップの入力", + "pulldown": "プルダウンの入力", + "pwmout": "PWM出力", + "servo": "サーボ出力" + }, + "status": { + "stopped": "停止", + "closed": "切断", + "not-running": "停止中", + "not-available": "利用不可", + "na": "N/A : __value__", + "ok": "OK" + }, + "errors": { + "ignorenode": "Raspberry Pi固有のノードを無視しました", + "version": "バージョンコマンドが失敗しました", + "sawpitype": "Saw Pi Type", + "libnotfound": "RPi.GPIO pythonライブラリを見つけられませんでした", + "alreadyset": "GPIO端子 __pin__ は既に出力形式が設定されています: __type__", + "invalidpin": "GPIO端子が不正です", + "invalidinput": "入力が不正です", + "needtobeexecutable": "__command__ は実行可能である必要があります", + "mustbeexecutable": "nrgpio は実行可能である必要があります", + "commandnotfound": "nrgpio コマンドが見つかりません", + "commandnotexecutable": "nrgpio コマンドが実行可能ではありません", + "error": "エラー: __error__", + "pythoncommandnotfound": "nrgpio python コマンドが実行されていません" + } + } +} 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..c72cb84b --- /dev/null +++ b/hardware/PiGpio/locales/ko/36-rpi-gpio.html @@ -0,0 +1,71 @@ + + + + + + + + + diff --git a/hardware/PiGpio/locales/ko/36-rpi-gpio.json b/hardware/PiGpio/locales/ko/36-rpi-gpio.json new file mode 100644 index 00000000..323ae882 --- /dev/null +++ b/hardware/PiGpio/locales/ko/36-rpi-gpio.json @@ -0,0 +1,73 @@ +"rpi-gpio": { + "label": { + "gpiopin": "GPIO", + "selectpin": "단자의 선택", + "resistor": "저항", + "readinitial": "배포나 재시작시에 단자의 초기상태를 불러옴", + "type": "출력형식", + "initpin": "단자의 상태를 초기화", + "debounce": "디바운스", + "freq": "빈도", + "button": "버튼", + "pimouse": "Pi Mouse", + "pikeyboard": "Pi Keyboard", + "left": "Left", + "right": "Right", + "middle": "Middle" + }, + "resistor": { + "none": "없음", + "pullup": "풀 업", + "pulldown": "풀 다운" + }, + "digout": "디지털 출력", + "pwmout": "PWM 출력", + "servo": "서보 출력", + "initpin0": "단자의 초기레벨 - Low (0)", + "initpin1": "단자의 초기레벨 - High (1)", + "left": "좌", + "right": "우", + "middle": "중간", + "any": "모두", + "pinname": "단자", + "alreadyuse": "사용중", + "alreadyset": "설정됨", + "tip": { + "pin": "사용중인 단자: ", + "in": "주석: 입력값은, 0 혹은 1의 수치만 대응하고 있습니다.", + "dig": "주석: ’출력형식’으로 ’디지털출력’을 사용하는 경우, 입력값은 0 혹은 1의 수치일 필요가 있습니다.", + "pwm": "주석: ’출력형식’으로 ’PWM출력’을 사용하는 경우, 입력값은 0~100의 수치일 필요가 있습니다.", + "ser": "주석: 서보 출력용 - 입력값은 0~100 사이일 필요가 있습니다. 50이 중심값입니다." + }, + "types": { + "digout": "디지털 출력", + "input": "입력", + "pullup": "풀 업 입력", + "pulldown": "풀 다운 입력", + "pwmout": "PWM 출력", + "servo": "서보 출력" + }, + "status": { + "stopped": "정지", + "closed": "절단", + "not-running": "정지중", + "not-available": "이용불가", + "na": "N/A : __value__", + "ok": "OK" + }, + "errors": { + "ignorenode": "Raspberry Pi고유의 노드를 무시했습니다", + "version": "버젼커맨드에 실패했습니다", + "sawpitype": "Saw Pi Type", + "libnotfound": "RPi.GPIO python라이브러리를 발견하지 못했습니다", + "alreadyset": "GPIO단자 __pin__ 은 이미 출력형식이 설정되어 있습니다: __type__", + "invalidpin": "GPIO단자가 올바르지 않습니다", + "invalidinput": "입력이 올바르지 않습니다", + "needtobeexecutable": "__command__ 은 실행가능상태일 필요가 있습니다 ", + "mustbeexecutable": "nrgpio 은 실행가능상태일 필요가 있습니다 ", + "commandnotfound": "nrgpio 커맨드를 찾을수 없습니다", + "commandnotexecutable": "nrgpio 커맨드가 실행가능상태가 아닙니다", + "error": "에러: __error__", + "pythoncommandnotfound": "nrgpio python 커맨드가 실행되지 않았습니다" + } +} 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..52ee3ab0 --- /dev/null +++ b/hardware/PiGpio/nrgpio.py @@ -0,0 +1,244 @@ +#!/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): + if bounce > 0: + 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) + + if bounce > 0: + GPIO.add_event_detect(pin, GPIO.BOTH, callback=handle_callback, bouncetime=int(bounce)) + else : + GPIO.add_event_detect(pin, GPIO.BOTH, callback=handle_callback) + sleep(0.1) + print(GPIO.input(pin)) + + 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..4331e228 --- /dev/null +++ b/hardware/PiGpio/package.json @@ -0,0 +1,28 @@ +{ + "name": "node-red-node-pi-gpio", + "version": "1.2.3", + "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/hardware/PiGpio/testgpio.py b/hardware/PiGpio/testgpio.py new file mode 100755 index 00000000..57305fd2 --- /dev/null +++ b/hardware/PiGpio/testgpio.py @@ -0,0 +1,7 @@ +#!/usr/bin/python +import sys +try: + import RPi.GPIO as GPIO + sys.exit(0) +except ImportError: + sys.exit(1) diff --git a/hardware/PiLcd/LICENSE b/hardware/PiLcd/LICENSE index f5b60114..f144b137 100644 --- a/hardware/PiLcd/LICENSE +++ b/hardware/PiLcd/LICENSE @@ -1,4 +1,4 @@ -Copyright 2016 JS Foundation and other contributors, https://js.foundation/ +Copyright 2016,2020 JS Foundation and other contributors, https://js.foundation/ Copyright 2013-2016 IBM Corp. Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/hardware/PiLcd/README.md b/hardware/PiLcd/README.md index 0f8d5468..a2b3aeaf 100644 --- a/hardware/PiLcd/README.md +++ b/hardware/PiLcd/README.md @@ -1,18 +1,16 @@ -node-red-node-pilcd -=================== +# node-red-node-pilcd A Node-RED node for a Raspberry Pi to write to a GPIO connected HD44780 style LCD panels. -Install -------- +## Install -Run the following command in your Node-RED user directory - typically `~/.node-red` +Either use the Node-RED Menu - Manage Palette option to install, or run the following +command in your Node-RED user directory - typically `~/.node-red` npm install node-red-node-pilcd -Usage ------ +## Usage Raspberry Pi output to HD44780 style LCD module - typically 1, 2, or 4 lines. diff --git a/hardware/PiLcd/locales/en-US/pilcd.html b/hardware/PiLcd/locales/en-US/pilcd.html new file mode 100644 index 00000000..966815b6 --- /dev/null +++ b/hardware/PiLcd/locales/en-US/pilcd.html @@ -0,0 +1,9 @@ + diff --git a/hardware/PiLcd/locales/en-US/pilcd.json b/hardware/PiLcd/locales/en-US/pilcd.json new file mode 100644 index 00000000..bf55ec30 --- /dev/null +++ b/hardware/PiLcd/locales/en-US/pilcd.json @@ -0,0 +1,19 @@ +{ + "pilcd": { + "label": { + "pins": "Pins" + }, + "tip": { + "tip": "Tip: Pins MUST be a comma separated list of the 6 GPIO connector pin numbers that are connected to the RS, E, D4, D5, D6 and D7 pins of the LCD." + }, + "status": { + "not-available": "not available", + "na": "N/A : __value__" + }, + "errors": { + "ignorenode": "Raspberry Pi specific node set inactive", + "libnotfound": "Cannot find Pi RPi.GPIO python library", + "needtobeexecutable": "__command__ needs to be executable" + } + } +} diff --git a/hardware/PiLcd/locales/ja/pilcd.html b/hardware/PiLcd/locales/ja/pilcd.html new file mode 100644 index 00000000..b5f26db3 --- /dev/null +++ b/hardware/PiLcd/locales/ja/pilcd.html @@ -0,0 +1,9 @@ + diff --git a/hardware/PiLcd/locales/ja/pilcd.json b/hardware/PiLcd/locales/ja/pilcd.json new file mode 100644 index 00000000..ba861599 --- /dev/null +++ b/hardware/PiLcd/locales/ja/pilcd.json @@ -0,0 +1,19 @@ +{ + "pilcd": { + "label": { + "pins": "ピン" + }, + "tip": { + "tip": "注釈: LCDのRS、E、D4、D5、D6、およびD7ピンに接続されている6つのGPIOコネクタのピン番号を、コンマで区切りでピンに入力する必要があります。" + }, + "status": { + "not-available": "利用不可", + "na": "N/A : __value__" + }, + "errors": { + "ignorenode": "Raspberry Pi固有のノードを無視しました", + "libnotfound": "RPi.GPIO pythonライブラリを見つけられませんでした", + "needtobeexecutable": "__command__ は実行可能である必要があります" + } + } +} diff --git a/hardware/PiLcd/nrlcd.py b/hardware/PiLcd/nrlcd.py index 623372da..0ab76eb8 100755 --- a/hardware/PiLcd/nrlcd.py +++ b/hardware/PiLcd/nrlcd.py @@ -11,7 +11,13 @@ import RPi.GPIO as GPIO import time import sys -import os, select +import os +import select + +try: + raw_input # Python 2 +except NameError: + raw_input = input # Python 3 # Turn off warnings if you run it a second time... GPIO.setwarnings(False) @@ -31,21 +37,22 @@ LCD_CMD = False LCD_LINE_1 = 0x80 # LCD RAM address for the 1st line LCD_LINE_2 = 0xC0 # LCD RAM address for the 2nd line -LCD_LINE_3 = 0xA0 # LCD RAM address for the 3rd line -LCD_LINE_4 = 0xE0 # LCD RAM address for the 4th line +LCD_LINE_3 = 0x94 # LCD RAM address for the 3rd line +LCD_LINE_4 = 0xD4 # LCD RAM address for the 4th line # Timing constants -E_PULSE = 0.00005 -E_DELAY = 0.00005 +E_PULSE = 0.0005 +E_DELAY = 0.0005 def lcd_init(): # Initialise display lcd_byte(0x33,LCD_CMD) lcd_byte(0x32,LCD_CMD) - lcd_byte(0x28,LCD_CMD) lcd_byte(0x0C,LCD_CMD) lcd_byte(0x06,LCD_CMD) + lcd_byte(0x28,LCD_CMD) lcd_byte(0x01,LCD_CMD) + time.sleep(E_DELAY) def lcd_string(message): # Send string to display @@ -106,8 +113,8 @@ def lcd_byte(bits, mode): if len(sys.argv) > 1: pins = sys.argv[1].lower().split(',') if len(pins) != 6: - print "Bad number of pins supplied" - print " "+pins + print("Bad number of pins supplied") + print(" "+pins) sys.exit(0) LCD_RS = int(pins[0]) @@ -117,6 +124,7 @@ if len(sys.argv) > 1: LCD_D6 = int(pins[4]) LCD_D7 = int(pins[5]) + GPIO.setwarnings(False) GPIO.setmode(GPIO.BOARD) # Use GPIO BOARD numbers GPIO.setup(LCD_RS, GPIO.OUT) # RS GPIO.setup(LCD_E, GPIO.OUT) # E @@ -179,6 +187,6 @@ if len(sys.argv) > 1: sys.exit(0) else: - print "Bad params" - print " sudo nrlcd.py RS,E,D4,D5,D6,D7" + print("Bad params") + print(" sudo nrlcd.py RS,E,D4,D5,D6,D7") sys.exit(0) diff --git a/hardware/PiLcd/package.json b/hardware/PiLcd/package.json index 39be00f5..8ec2355b 100644 --- a/hardware/PiLcd/package.json +++ b/hardware/PiLcd/package.json @@ -1,6 +1,6 @@ { "name" : "node-red-node-pilcd", - "version" : "0.0.6", + "version" : "0.1.0", "description" : "A Node-RED node for Raspberry Pi to write to HD44780 style LCD panels.", "dependencies" : { }, diff --git a/hardware/PiLcd/pilcd.html b/hardware/PiLcd/pilcd.html index faa4b475..2b703018 100644 --- a/hardware/PiLcd/pilcd.html +++ b/hardware/PiLcd/pilcd.html @@ -1,26 +1,14 @@ - - - - - @@ -64,12 +87,15 @@ color:"#c6dbef", defaults: { name: { value:"" }, + gpio: { value:18 }, pixels: { value:"", required:true, validate:RED.validators.number() }, bgnd: { value:"" }, fgnd: { value:"" }, wipe: { value:"40", required:true, validate:RED.validators.number() }, mode: { value:"pcent" }, - rgb: { value:"rgb" } + rgb: { value:"rgb" }, + brightness: { value:"100", required:true, validate:RED.validators.number() }, + gamma: { value: true } }, inputs:1, outputs:0, @@ -82,6 +108,14 @@ return this.name?"node_label_italic":""; }, oneditprepare: function() { + if (this.gamma === undefined) { + this.gamma = true; + $("#node-input-gamma").prop('checked', true); + } + if (this.brightness === undefined) { + this.brighness = "100"; + $("#node-input-brightness").val("100"); + } var setstate = function () { if ($('#node-input-mode').val().indexOf("shift") !== -1) { $("#bgcol").hide(); diff --git a/hardware/neopixel/neopixel.js b/hardware/neopixel/neopixel.js index 85980bb2..12214b33 100644 --- a/hardware/neopixel/neopixel.js +++ b/hardware/neopixel/neopixel.js @@ -6,23 +6,26 @@ module.exports = function(RED) { var fs = require('fs'); var colors = require('./colours.js'); var piCommand = __dirname+'/neopix'; + var allOK = true; try { var cpuinfo = fs.readFileSync("/proc/cpuinfo").toString(); - if (cpuinfo.indexOf(": BCM") === -1) { throw "Info : "+RED._("rpi-gpio.errors.ignorenode"); } + if (cpuinfo.indexOf(": BCM") === -1) { + RED.log.warn("rpi-neopixels : "+RED._("node-red:rpi-gpio.errors.ignorenode")); + allOK = false; + } + else if (execSync('python -c "import rpi_ws281x"').toString() !== "") { + RED.log.warn("rpi-neopixels : Can't find neopixel python library"); + allOK = false; + } + else if (!(1 & parseInt ((fs.statSync(piCommand).mode & parseInt ("777", 8)).toString (8)[0]))) { + RED.log.warn("rpi-neopixels : "+RED._("node-red:rpi-gpio.errors.needtobeexecutable",{command:piCommand})); + allOK = false; + } } catch(err) { - throw "Info : "+RED._("rpi-gpio.errors.ignorenode"); - } - - if (execSync('python -c "import neopixel"').toString() !== "") { - RED.log.warn("Can't find neopixel python library"); - throw "Warning : Can't find neopixel python library"; - } - - if ( !(1 & parseInt ((fs.statSync(piCommand).mode & parseInt ("777", 8)).toString (8)[0]) )) { - RED.log.error(piCommand + " command is not executable"); - throw "Error : "+RED._("rpi-gpio.errors.mustbeexecutable"); + RED.log.warn("rpi-neopixels : "+RED._("node-red:rpi-gpio.errors.ignorenode")); + allOK = false; } // the magic to make python print stuff immediately @@ -35,8 +38,16 @@ module.exports = function(RED) { this.fgnd = n.fgnd || "128,128,128"; this.mode = n.mode || "pcent"; this.rgb = n.rgb || "rgb"; + this.gamma = n.gamma; + if (this.gamma === undefined) { this.gamma = true; } + this.gpio = n.gpio || 18; + this.channel = 0; + if (this.gpio == 13 || this.gpio == 19) { this.channel = 1; } + this.brightness = Number(n.brightness || 100); this.wipe = Number(n.wipe || 40); if (this.wipe < 0) { this.wipe = 0; } + if (this.brightness < 0) { this.brightness = 0; } + if (this.brightness > 100) { this.brightness = 100; } var node = this; var needle = "255,255,255"; //var p1 = /^\#[A-Fa-f0-9]{6}$/ @@ -45,6 +56,9 @@ module.exports = function(RED) { var p4 = /^[0-9]+,[0-9]+,[0-9]+,[0-9]+,[0-9]+$/ function inputlistener(msg) { + if (msg.hasOwnProperty("brightness")) { + node.child.stdin.write("brightness,"+msg.brightness.toString()+"\n"); + } if (msg.hasOwnProperty("payload")) { var pay = msg.payload.toString().toUpperCase(); var parts = pay.split(","); @@ -102,57 +116,62 @@ module.exports = function(RED) { } } - node.child = spawn(piCommand, [node.pixels, node.wipe, node.mode]); - node.status({fill:"green",shape:"dot",text:"ok"}); + if (allOK === true) { + node.child = spawn(piCommand, [node.pixels, node.wipe, node.mode, node.brightness, node.gamma, node.channel, node.gpio]); + node.status({fill:"green",shape:"dot",text:"ok"}); - node.on("input", inputlistener); + node.on("input", inputlistener); - node.child.stdout.on('data', function (data) { - if (RED.settings.verbose) { node.log("out: "+data+" :"); } - }); + 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.stderr.on('data', function (data) { + if (RED.settings.verbose) { node.log("err: "+data+" :"); } + }); - node.child.on('close', function () { - node.child = null; - if (RED.settings.verbose) { node.log(RED._("rpi-gpio.status.closed")); } - if (node.done) { + node.child.on('close', function () { + node.child = null; + if (RED.settings.verbose) { node.log(RED._("rpi-gpio.status.closed")); } + if (node.finished) { + node.status({fill:"grey",shape:"ring",text:"closed"}); + node.finished(); + } + else { node.status({fill:"red",shape:"ring",text:"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:"closed"}); - node.done(); - } - else { node.status({fill:"red",shape:"ring",text:"stopped"}); } - }); + if (node.child != null) { + node.finished = done; + node.child.kill('SIGKILL'); + } + else { done(); } + }); - 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:"closed"}); - if (node.child != null) { - node.done = done; - node.child.kill('SIGKILL'); + if (node.bgnd) { + if (node.bgnd.split(',').length === 1) { + node.bgnd = colors.getRGB(node.bgnd,node.rgb); + } + if (node.mode.indexOf("shift") === -1) { + node.child.stdin.write(node.bgnd+"\n"); + } } - else { done(); } - }); - if (node.bgnd) { - if (node.bgnd.split(',').length === 1) { - node.bgnd = colors.getRGB(node.bgnd,node.rgb); - } - if (node.mode.indexOf("shift") === -1) { - node.child.stdin.write(node.bgnd+"\n"); + if (node.fgnd) { + if (node.fgnd.split(',').length === 1) { + node.fgnd = colors.getRGB(node.fgnd,node.rgb); + } } } - - if (node.fgnd) { - if (node.fgnd.split(',').length === 1) { - node.fgnd = colors.getRGB(node.fgnd,node.rgb); - } + else { + node.status({fill:"grey",shape:"dot",text:"node-red:rpi-gpio.status.not-available"}); } } RED.nodes.registerType("rpi-neopixels",PiNeopixelNode); diff --git a/hardware/neopixel/package.json b/hardware/neopixel/package.json index 2f8e5e20..6a1e65eb 100644 --- a/hardware/neopixel/package.json +++ b/hardware/neopixel/package.json @@ -1,6 +1,6 @@ { "name" : "node-red-node-pi-neopixel", - "version" : "0.0.16", + "version" : "0.1.1", "description" : "A Node-RED node to output to a neopixel (ws2812) string of LEDS from a Raspberry Pi.", "dependencies" : { }, @@ -9,14 +9,18 @@ "url":"https://github.com/node-red/node-red-nodes/tree/master/hardware/neopixel" }, "license": "Apache-2.0", - "keywords": [ "node-red", "ws2812", "neopixel" ], - "node-red" : { - "nodes" : { + "keywords": [ + "node-red", + "ws2812", + "neopixel" + ], + "node-red": { + "nodes": { "rpi-neopixels": "neopixel.js" } }, - "scripts" : { - "postinstall" : "scripts/checklib.js" + "scripts": { + "postinstall": "scripts/checklib.js" }, "author": { "name": "Dave Conway-Jones", diff --git a/hardware/pigpiod/README.md b/hardware/pigpiod/README.md index d6bdadeb..c69db58b 100644 --- a/hardware/pigpiod/README.md +++ b/hardware/pigpiod/README.md @@ -2,9 +2,11 @@ node-red-node-pi-gpiod ====================== An alternative pair of Node-RED nodes to interact with Pi GPIO using -the PiGPIOd daemon that is now part of Raspbian. +the PiGPIOd daemon that is now part of Raspbian. -The advantage is that it also talk to GPIO on a Pi that is remote as long as it is running the daemon, and also sharing pins works more cleanly as contention is handled by the multiple connections. +The advantage is that it also talk to GPIO on a Pi that is remote as long as it is running the daemon, and also sharing pins works more cleanly as contention is handled by the multiple connections. This is also a +good way to access GPIO when running Docker on a Pi as you can use the network connection to link out of +the container to the PiGPIO daemon running on the host. The disadvantage is that you must setup and run the PiGPIO daemon first. @@ -19,7 +21,7 @@ PiGPIOd must be running on the pi. The easiest way to ensure this is to add the /usr/bin/pigpiod -l /usr/bin/pigpiod -n 192.168.1.10 -See the instructions for more details. +See the instructions for more details. ## Install diff --git a/hardware/pigpiod/locales/en-US/pi-gpiod.json b/hardware/pigpiod/locales/en-US/pi-gpiod.json index 69199beb..5eaa33e6 100644 --- a/hardware/pigpiod/locales/en-US/pi-gpiod.json +++ b/hardware/pigpiod/locales/en-US/pi-gpiod.json @@ -30,10 +30,10 @@ "pinname": "Pin", "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.", - "ser": "Tip: For Servo output - input must be between 0 to 100. 50 is centre.Raspberry Pi Sense HAT output node.
This node sends commands to the 8x8 LED display on the Sense HAT.
Commands are sent to the node in msg.payload
. Multiple commands can
-be sent in a single message by separating them with newline (\n) characters.
+be sent in a single message by separating them with newline (\n) characters. You must +use a function node or a change node (jsonata expression) when using the newline (\n) +character to create a payload containing multiple commands.
Set the colour of individual pixels
Format: <x>,<y>,<colour>
@@ -100,11 +102,13 @@ be sent in a single message by separating them with newline (\n) characters.
Rotate the screen
Format: R<angle>
angle
must be 0, 90, 180 or 270.
Example: R180
Flip the screen
Format: F<axis>
axis
must be either H
or V
to flip on
the horizontal or vertical axis respectively.
Example: FV
Scroll a message
If msg.payload
is not recognised as any of the above commands,
@@ -121,6 +125,7 @@ To scroll a single character, append a blank space after it - "A "
.
Set the screen brightness
Format: D<level>
level
must be 0 (low) or 1 (high).
Example: D1