From 4b3db1e780c91dcec513d75beb9ea4a0d962f249 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Sun, 16 Mar 2014 21:03:14 +0000 Subject: [PATCH 01/16] Update suncalc to match module api changes --- time/79-suncalc.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/time/79-suncalc.js b/time/79-suncalc.js index 2c5e8b54..2b265391 100644 --- a/time/79-suncalc.js +++ b/time/79-suncalc.js @@ -38,7 +38,7 @@ function SunNode(n) { var mins2 = times[node.end].getUTCMinutes(); var e1 = (hour*60+mins) - (hour1*60+mins1); var e2 = (hour*60+mins) - (hour2*60+mins2); - var moon = parseInt(SunCalc.getMoonFraction(now)*100)/100; + var moon = SunCalc.getMoonIllumination(now).fraction; msg = { payload:0, topic:"sun", moon:moon }; if ((e1 > 0) & (e2 < 0)) { msg.payload = 1; } if (oldval == null) { oldval = msg.payload; } From 0d9555ff339bd02c9c8d2fc400b4d95c43b283e3 Mon Sep 17 00:00:00 2001 From: hdoukas Date: Wed, 26 Mar 2014 10:01:32 +0100 Subject: [PATCH 02/16] code re-write, added functionality for setting brightness, lamp parameters can be also set through node input --- hardware/hue/103-hue_discover.html | 23 +++--- hardware/hue/104-hue_manage.html | 27 ++++--- hardware/hue/104-hue_manage.js | 126 +++++++++++++++++++---------- 3 files changed, 113 insertions(+), 63 deletions(-) diff --git a/hardware/hue/103-hue_discover.html b/hardware/hue/103-hue_discover.html index db540a57..ac6c1d39 100644 --- a/hardware/hue/103-hue_discover.html +++ b/hardware/hue/103-hue_discover.html @@ -13,6 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. + --> + + + + + + + + + + + diff --git a/hardware/Pi/38-rpi-pibrella.js b/hardware/Pi/38-rpi-pibrella.js new file mode 100644 index 00000000..140b2286 --- /dev/null +++ b/hardware/Pi/38-rpi-pibrella.js @@ -0,0 +1,173 @@ +/** + * Copyright 2013 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. + **/ + +var RED = require(process.env.NODE_RED_HOME+"/red/red"); +var util = require("util"); +var exec = require('child_process').exec; +var fs = require('fs'); + +if (!fs.existsSync("/usr/local/bin/gpio")) { + exec("cat /proc/cpuinfo | grep BCM27",function(err,stdout,stderr) { + if (stdout.indexOf('BCM27') > -1) { + util.log('[36-rpi-gpio.js] Error: Cannot find Wiring-Pi "gpio" command. http://wiringpi.com/download-and-install/'); + } + // else not on a Pi so don't worry anyone with needless messages. + }); + return; +} + +// Map physical P1 pins to Gordon's Wiring-Pi Pins (as they should be V1/V2 tolerant) +var pintable = { +// Physical : WiringPi + "Amber LED":"0", + "Buzzer":"1", + "Red LED":"2", + "Out E":"3", + "Out F":"4", + "Out G":"5", + "Out H":"6", + "Green LED":"7", + "In C":"10", + "In B":"11", + "In D":"12", + "In A":"13", + "Red Button":"14", +} +var tablepin = { +// WiringPi : Physical + "0":"Amber", + "Buzzer":"1", + "2":"Red", + "3":"E", + "4":"F", + "5":"G", + "6":"H", + "7":"Green", + "10":"C", + "11":"B", + "12":"D", + "13":"A", + "14":"R", +} + +function PibrellaIn(n) { + RED.nodes.createNode(this,n); + this.buttonState = -1; + this.pin = pintable[n.pin]; + this.intype = n.intype; + var node = this; + + if (this.pin) { + exec("gpio mode "+node.pin+" "+node.intype, function(err,stdout,stderr) { + if (err) node.error(err); + else { + node._interval = setInterval( function() { + exec("gpio read "+node.pin, function(err,stdout,stderr) { + if (err) node.error(err); + else { + if (node.buttonState !== Number(stdout)) { + var previousState = node.buttonState; + node.buttonState = Number(stdout); + if (previousState !== -1) { + var msg = {topic:"pibrella/"+tablepin[node.pin], payload:node.buttonState}; + node.send(msg); + } + } + } + }); + }, 250); + } + }); + } + else { + this.error("Invalid GPIO pin: "+this.pin); + } +} + +function PibrellaOut(n) { + RED.nodes.createNode(this,n); + this.pin = pintable[n.pin]; + var node = this; + + if (this.pin == "1") { + exec("gpio mode 1 pwm"); + process.nextTick(function() { + exec("gpio pwm-ms"); + node.on("input", function(msg) { + var out = Number(msg.payload); + if (out == 1) { out = 2; } // doesn't work with 1... + if (out == 0) { exec("gpio pwm 1 0"); } + else { + exec("gpio pwm 1 511"); + exec("gpio pwmc "+out); + } + }); + }); + } + else if (this.pin) { + process.nextTick(function() { + exec("gpio mode "+node.pin+" out", function(err,stdout,stderr) { + if (err) node.error(err); + else { + node.on("input", function(msg) { + if (msg.payload === "true") msg.payload = true; + if (msg.payload === "false") msg.payload = false; + var out = Number(msg.payload); + if ((out == 0)|(out == 1)) { + exec("gpio write "+node.pin+" "+out, function(err,stdout,stderr) { + if (err) node.error(err); + }); + } + else node.warn("Invalid input - not 0 or 1"); + }); + } + }); + }); + } + else { + this.error("Invalid GPIO pin: "+this.pin); + } +} + +exec("gpio mode 0 out",function(err,stdout,stderr) { + if (err) { + util.log('[36-rpi-gpio.js] Error: "gpio" command failed for some reason.'); + } + exec("gpio mode 1 out"); + exec("gpio mode 2 out"); + exec("gpio mode 3 out"); + exec("gpio mode 4 out"); + exec("gpio mode 5 out"); + exec("gpio mode 6 out"); + exec("gpio mode 7 out"); + exec("gpio mode 10 in"); + exec("gpio mode 11 in"); + exec("gpio mode 12 in"); + exec("gpio mode 13 in"); + exec("gpio mode 14 in",function(err,stdout,stderr) { + RED.nodes.registerType("rpi-pibrella in",PibrellaIn); + RED.nodes.registerType("rpi-pibrella out",PibrellaOut); + + PibrellaIn.prototype.close = function() { + clearInterval(this._interval); + } + + PibrellaOut.prototype.close = function() { + exec("gpio mode "+this.pin+" in"); + } + + }); +}); From e8c328a9e1e75240317b2300ff12caf47329a5b3 Mon Sep 17 00:00:00 2001 From: Dave C-J Date: Mon, 31 Mar 2014 22:37:28 +0100 Subject: [PATCH 06/16] Tweaks to verify input to Pirella Node, remove unused variable. --- hardware/Pi/38-rpi-pibrella.html | 11 +++++------ hardware/Pi/38-rpi-pibrella.js | 9 ++++----- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/hardware/Pi/38-rpi-pibrella.html b/hardware/Pi/38-rpi-pibrella.html index 73a3802d..3a24e97e 100644 --- a/hardware/Pi/38-rpi-pibrella.html +++ b/hardware/Pi/38-rpi-pibrella.html @@ -18,7 +18,7 @@
- + @@ -73,7 +72,7 @@ - +
@@ -96,7 +95,7 @@ color:"#c6dbef", defaults: { name: { value:"" }, - pin: { value:"",required:true }, + pin: { value:"",required:true,validate:RED.validators.regex(/ /) } }, inputs:1, outputs:0, diff --git a/hardware/Pi/38-rpi-pibrella.js b/hardware/Pi/38-rpi-pibrella.js index 140b2286..48e8b23d 100644 --- a/hardware/Pi/38-rpi-pibrella.js +++ b/hardware/Pi/38-rpi-pibrella.js @@ -33,7 +33,7 @@ if (!fs.existsSync("/usr/local/bin/gpio")) { var pintable = { // Physical : WiringPi "Amber LED":"0", - "Buzzer":"1", + "Buzzer ":"1", "Red LED":"2", "Out E":"3", "Out F":"4", @@ -49,7 +49,7 @@ var pintable = { var tablepin = { // WiringPi : Physical "0":"Amber", - "Buzzer":"1", + "1":"Buzzer", "2":"Red", "3":"E", "4":"F", @@ -67,11 +67,10 @@ function PibrellaIn(n) { RED.nodes.createNode(this,n); this.buttonState = -1; this.pin = pintable[n.pin]; - this.intype = n.intype; var node = this; if (this.pin) { - exec("gpio mode "+node.pin+" "+node.intype, function(err,stdout,stderr) { + exec("gpio mode "+node.pin+" in", function(err,stdout,stderr) { if (err) node.error(err); else { node._interval = setInterval( function() { @@ -88,7 +87,7 @@ function PibrellaIn(n) { } } }); - }, 250); + }, 200); } }); } From 3da2a29b5e3d2f74aa696c34e99f75a28c659f8b Mon Sep 17 00:00:00 2001 From: Dave C-J Date: Tue, 1 Apr 2014 10:45:54 +0100 Subject: [PATCH 07/16] Set BBB nodes to only load if bonescript present. (ie on a BBB) --- hardware/BBB/145-BBB-hardware.js | 41 ++++++++++++++++---------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/hardware/BBB/145-BBB-hardware.js b/hardware/BBB/145-BBB-hardware.js index 70205a48..4df3a04d 100644 --- a/hardware/BBB/145-BBB-hardware.js +++ b/hardware/BBB/145-BBB-hardware.js @@ -16,13 +16,14 @@ // Require main module var RED = require(process.env.NODE_RED_HOME + "/red/red"); +var bonescript = require("bonescript"); // Require bonescript -try { - var bonescript = require("bonescript"); -} catch (err) { - require("util").log("[145-BBB-hardware] Error: cannot find module 'bonescript'"); -} +//try { +// var bonescript = require("bonescript"); +//} catch (err) { +// require("util").log("[145-BBB-hardware] Error: cannot find module 'bonescript'"); +//} // Node constructor for bbb-analogue-in function AnalogueInputNode(n) { @@ -46,7 +47,7 @@ function AnalogueInputNode(n) { // Variables used for input averaging var sum; // accumulates the input readings to be averaged var count; // keep track of the number of measurements made - + // The callback function for analogRead. Accumulates the required number of // measurements, then divides the total number, applies output scaling and // sends the result @@ -113,11 +114,11 @@ function DiscreteInputNode(n) { this.starting = true; this.debouncing = false; // True after a change of state while waiting for the 7ms debounce time to elapse this.debounceTimer = null; - + // Define 'node' to allow us to access 'this' from within callbacks var node = this; - // This function is called by the input pin change-of-state interrupt. If + // This function is called by the input pin change-of-state interrupt. If // debounce is disabled, send the output message. Otherwise, if we are // currently debouncing, ignore this interrupt. If we are not debouncing, // schedule a re-read of the input pin in 7ms time, and set the debouncing flag @@ -146,7 +147,7 @@ function DiscreteInputNode(n) { sendStateMessage(x); } }; - + // This function is called when either the interruptCallback or the debounceCallback // have determined we have a 'genuine' change of state. Update the currentState and // ActiveTime variables, and send a message on the first output with the new state @@ -165,7 +166,7 @@ function DiscreteInputNode(n) { node.send([msg, null]); } }; - + // This function is called by the timer. It updates the ActiveTime variables, and sends a // message on the second output with the latest value of the total active time, in seconds var timerCallback = function () { @@ -270,7 +271,7 @@ function PulseInputNode(n) { // Define 'node' to allow us to access 'this' from within callbacks var node = this; - // Called by the edge or pulse interrupt. If this is a valid interrupt, record the + // Called by the edge or pulse interrupt. If this is a valid interrupt, record the // pulse time and count the pulse var interruptCallback = function (x) { if (x.value !== undefined) { @@ -350,12 +351,12 @@ function DiscreteOutputNode(n) { this.defaultState = Number(n.defaultState); // What state to set up as this.inverting = n.inverting; this.toggle = n.toggle; - + // Working variables this.currentState = this.defaultState; - + var node = this; - + // If the input message paylod is numeric, values > 0.5 are 'true', otherwise use // the truthiness of the payload. Apply the inversion flag before setting the output var inputCallback = function (msg) { @@ -378,7 +379,7 @@ function DiscreteOutputNode(n) { node.send({ topic:node.topic, payload:newState }); node.currentState = newState; }; - + // If we have a valid pin, set it as an output and set the default state if (["P8_7", "P8_8", "P8_9", "P8_10", "P8_11", "P8_12", "P8_13", "P8_14", "P8_15", "P8_16", "P8_17", "P8_18", "P8_19", "P8_26", "P9_11", "P9_12", "P9_13", "P9_14", @@ -407,12 +408,12 @@ function PulseOutputNode(n) { this.defaultState = this.pulseState === 1 ? 0 : 1; this.retriggerable = n.retriggerable; this.pulseTime = n.pulseTime * 1000; // Pulse width in milliseconds - + // Working variables this.pulseTimer = null; // Non-null while a pulse is being generated - + var node = this; - + // Generate a pulse in response to an input message. If the topic includes the text // 'time' (case insensitive) and the payload is numeric, use this value as the // pulse time. Otherwise use the value from the properties dialog. @@ -444,14 +445,14 @@ function PulseOutputNode(n) { } } }; - + // At the end of the pulse, restore the default state and set the timer to null var endPulseCallback = function () { node.pulseTimer = null; bonescript.digitalWrite(node.pin, node.defaultState); node.send({ topic:node.topic, payload:node.defaultState }); }; - + // If we have a valid pin, set it as an output and set the default state if (["P8_7", "P8_8", "P8_9", "P8_10", "P8_11", "P8_12", "P8_13", "P8_14", "P8_15", "P8_16", "P8_17", "P8_18", "P8_19", "P8_26", "P9_11", "P9_12", "P9_13", "P9_14", From 32db5a7e68d3b5b1e1f0686a077027da0c2028b1 Mon Sep 17 00:00:00 2001 From: Dave C-J Date: Tue, 1 Apr 2014 10:46:18 +0100 Subject: [PATCH 08/16] update data to 2014 for copyright on new files. --- hardware/Pi/38-rpi-pibrella.html | 2 +- hardware/Pi/38-rpi-pibrella.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/hardware/Pi/38-rpi-pibrella.html b/hardware/Pi/38-rpi-pibrella.html index 3a24e97e..e6a8f5f9 100644 --- a/hardware/Pi/38-rpi-pibrella.html +++ b/hardware/Pi/38-rpi-pibrella.html @@ -1,5 +1,5 @@ + + diff --git a/social/twilio/56-twilio.js b/social/twilio/56-twilio.js index 8aca55ff..5db40726 100644 --- a/social/twilio/56-twilio.js +++ b/social/twilio/56-twilio.js @@ -17,47 +17,97 @@ var RED = require(process.env.NODE_RED_HOME+"/red/red"); var util = require('util'); - -// Either add a line like this to settings.js -// twilio: { account:'My-ACCOUNT-SID', authtoken:'TWILIO-TOKEN',from:'FROM-NUMBER' }, -// Or as a twiliokey.js file in the directory ABOVE node-red. -// module.exports = { account:'My-ACCOUNT-SID', authtoken:'TWILIO-TOKEN',from:'FROM-NUMBER' } +var twilio = require('twilio'); try { var twiliokey = RED.settings.twilio || require(process.env.NODE_RED_HOME+"/../twiliokey.js"); } catch(err) { - util.log("[56-twilio.js] Error: Failed to load Twilio credentials"); } -if (twiliokey) { - var twilioClient = require('twilio')(twiliokey.account, twiliokey.authtoken); - var fromNumber = twiliokey.from; -} +var querystring = require('querystring'); +RED.httpAdmin.get('/twilio-api/global',function(req,res) { + res.send(JSON.stringify({hasToken:!(twiliokey && twiliokey.account && twiliokey.authtoken)})); +}); +RED.httpAdmin.get('/twilio-api/:id',function(req,res) { + var credentials = RED.nodes.getCredentials(req.params.id); + if (credentials) { + res.send(JSON.stringify({hasToken:(credentials.token&&credentials.token!="")})); + } else { + res.send(JSON.stringify({})); + } +}); + +RED.httpAdmin.delete('/twilio-api/:id',function(req,res) { + RED.nodes.deleteCredentials(req.params.id); + res.send(200); +}); + +RED.httpAdmin.post('/twilio-api/:id',function(req,res) { + var body = ""; + req.on('data', function(chunk) { + body+=chunk; + }); + req.on('end', function(){ + var newCreds = querystring.parse(body); + var credentials = RED.nodes.getCredentials(req.params.id)||{}; + if (newCreds.token == "") { + delete credentials.token; + } else { + credentials.token = newCreds.token; + } + RED.nodes.addCredentials(req.params.id,credentials); + res.send(200); + }); +}); + +function TwilioAPINode(n) { + RED.nodes.createNode(this,n); + this.sid = n.sid; + this.from = n.from; + this.name = n.name; + var credentials = RED.nodes.getCredentials(n.id); + if (credentials) { + this.token = credentials.token; + } +} +RED.nodes.registerType("twilio-api",TwilioAPINode); + + function TwilioOutNode(n) { RED.nodes.createNode(this,n); this.number = n.number; + + this.api = RED.nodes.getNode(n.twilio); + + if (this.api) { + this.twilioClient = twilio(this.api.sid,this.api.token); + this.fromNumber = this.api.from; + } else if (twiliokey) { + this.twilioClient = twilio(twiliokey.account, twiliokey.authtoken); + this.fromNumber = twiliokey.from; + } else { + this.error("missing twilio credentials"); + return; + } + var node = this; this.on("input",function(msg) { if (typeof(msg.payload) == 'object') { msg.payload = JSON.stringify(msg.payload); } - if (twiliokey) { - try { - // Send SMS - var tonum = node.number || msg.topic; - twilioClient.sendMessage( {to: tonum, from: fromNumber, body: msg.payload}, function(err, response) { - if (err) node.error(err); - //console.log(response); - }); - } - catch (err) { - node.error(err); - } - } - else { - node.warn("Twilio credentials not set/found. See node info."); + try { + // Send SMS + var tonum = node.number || msg.topic; + node.twilioClient.sendMessage( {to: tonum, from: node.fromNumber, body: msg.payload}, function(err, response) { + if (err) { + node.error(err); + } + //console.log(response); + }); + } catch (err) { + node.error(err); } }); } From fc00b1d66efcdbd5f0fb9e4c7b28687688b290c8 Mon Sep 17 00:00:00 2001 From: hdoukas Date: Sat, 12 Apr 2014 11:32:23 +0200 Subject: [PATCH 16/16] fixed backwards compatibility These changes affect the settings of hue node through the message input. msg.lamp sets the lamp ID msg.color sets the lamp color (e.g., msg.color="DF0101" will set the color to red) msg.brightness sets the lamp brightness (e.g., msg.brightness=50) msg.payload is used to se the lamp status (on/off/alert) (e.g., msg.payload="alert" will flash the Lamp once) msg.topic can be still used to set the color (compatibility with previous versions) --- hardware/hue/104-hue_manage.html | 10 +++++++++- hardware/hue/104-hue_manage.js | 29 ++++++++++++++++------------- 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/hardware/hue/104-hue_manage.html b/hardware/hue/104-hue_manage.html index a506c6ed..d23f3973 100644 --- a/hardware/hue/104-hue_manage.html +++ b/hardware/hue/104-hue_manage.html @@ -56,7 +56,15 @@ diff --git a/hardware/hue/104-hue_manage.js b/hardware/hue/104-hue_manage.js index e01f4be5..9d1374f2 100644 --- a/hardware/hue/104-hue_manage.js +++ b/hardware/hue/104-hue_manage.js @@ -92,27 +92,30 @@ function HueNode(n) { if(node.lamp_status=="AUTO") { var color; var brightness; - //check for lamp ID in the topic - if(myMsg.topic.length>1) { - var tmp_status = myMsg.topic.split(":"); - myMsg.topic = tmp_status[1]; - lamp = tmp_status[0]; + + //get lamp id from msg.lamp: + lamp = myMsg.lamp; + + //get brightness: + brightness = myMsg.brightness; + + //get colour either from msg.color or msg.topic + if(myMsg.color!=null && myMsg.color.length>0) { + color = myMsg.color; + } + else if(myMsg.topic!=null && myMsg.topic.length>0) { + color = myMsg.topic; } - //check for brightness & color: - if(myMsg.payload.length>1) { - var tmp_topic = myMsg.payload.split(":"); - color = tmp_topic[0]; - brightness = tmp_topic[1]; - } + //check the payload for on/off/alert: //case of ALERT: - if(myMsg.topic=="ALERT"){ + if(myMsg.payload=="ALERT" || myMsg.payload=="alert"){ api.setLightState(lamp, state.alert()).then(displayResult).fail(displayError).done(); } //case of ON: - if(myMsg.topic=="ON") { + if(myMsg.payload=="ON" || myMsg.payload=="on") { api.setLightState(lamp, state.on().rgb(hexToRgb(color).r,hexToRgb(color).g,hexToRgb(color).b).brightness(brightness)).then(displayResult).fail(displayError).done(); } else {