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

Merge pull request #2 from node-red/master

Re-sync
This commit is contained in:
Max Hadley 2014-04-15 20:55:06 +01:00
commit de655a6dde
17 changed files with 697 additions and 169 deletions

View File

@ -16,13 +16,7 @@
// Require main module // Require main module
var RED = require(process.env.NODE_RED_HOME + "/red/red"); 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'");
}
// Node constructor for bbb-analogue-in // Node constructor for bbb-analogue-in
function AnalogueInputNode(n) { function AnalogueInputNode(n) {
@ -46,7 +40,7 @@ function AnalogueInputNode(n) {
// Variables used for input averaging // Variables used for input averaging
var sum; // accumulates the input readings to be averaged var sum; // accumulates the input readings to be averaged
var count; // keep track of the number of measurements made var count; // keep track of the number of measurements made
// The callback function for analogRead. Accumulates the required number of // The callback function for analogRead. Accumulates the required number of
// measurements, then divides the total number, applies output scaling and // measurements, then divides the total number, applies output scaling and
// sends the result // sends the result
@ -113,11 +107,11 @@ function DiscreteInputNode(n) {
this.starting = true; this.starting = true;
this.debouncing = false; // True after a change of state while waiting for the 7ms debounce time to elapse this.debouncing = false; // True after a change of state while waiting for the 7ms debounce time to elapse
this.debounceTimer = null; this.debounceTimer = null;
// Define 'node' to allow us to access 'this' from within callbacks // Define 'node' to allow us to access 'this' from within callbacks
var node = this; 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 // debounce is disabled, send the output message. Otherwise, if we are
// currently debouncing, ignore this interrupt. If we are not debouncing, // 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 // schedule a re-read of the input pin in 7ms time, and set the debouncing flag
@ -146,7 +140,7 @@ function DiscreteInputNode(n) {
sendStateMessage(x); sendStateMessage(x);
} }
}; };
// This function is called when either the interruptCallback or the debounceCallback // This function is called when either the interruptCallback or the debounceCallback
// have determined we have a 'genuine' change of state. Update the currentState and // 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 // ActiveTime variables, and send a message on the first output with the new state
@ -165,7 +159,7 @@ function DiscreteInputNode(n) {
node.send([msg, null]); node.send([msg, null]);
} }
}; };
// This function is called by the timer. It updates the ActiveTime variables, and sends a // 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 // message on the second output with the latest value of the total active time, in seconds
var timerCallback = function () { var timerCallback = function () {
@ -270,7 +264,7 @@ function PulseInputNode(n) {
// Define 'node' to allow us to access 'this' from within callbacks // Define 'node' to allow us to access 'this' from within callbacks
var node = this; 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 // pulse time and count the pulse
var interruptCallback = function (x) { var interruptCallback = function (x) {
if (x.value !== undefined) { if (x.value !== undefined) {
@ -350,12 +344,12 @@ function DiscreteOutputNode(n) {
this.defaultState = Number(n.defaultState); // What state to set up as this.defaultState = Number(n.defaultState); // What state to set up as
this.inverting = n.inverting; this.inverting = n.inverting;
this.toggle = n.toggle; this.toggle = n.toggle;
// Working variables // Working variables
this.currentState = this.defaultState; this.currentState = this.defaultState;
var node = this; var node = this;
// If the input message paylod is numeric, values > 0.5 are 'true', otherwise use // 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 // the truthiness of the payload. Apply the inversion flag before setting the output
var inputCallback = function (msg) { var inputCallback = function (msg) {
@ -378,7 +372,7 @@ function DiscreteOutputNode(n) {
node.send({ topic:node.topic, payload:newState }); node.send({ topic:node.topic, payload:newState });
node.currentState = newState; node.currentState = newState;
}; };
// If we have a valid pin, set it as an output and set the default state // 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", 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", "P8_16", "P8_17", "P8_18", "P8_19", "P8_26", "P9_11", "P9_12", "P9_13", "P9_14",
@ -407,12 +401,12 @@ function PulseOutputNode(n) {
this.defaultState = this.pulseState === 1 ? 0 : 1; this.defaultState = this.pulseState === 1 ? 0 : 1;
this.retriggerable = n.retriggerable; this.retriggerable = n.retriggerable;
this.pulseTime = n.pulseTime * 1000; // Pulse width in milliseconds this.pulseTime = n.pulseTime * 1000; // Pulse width in milliseconds
// Working variables // Working variables
this.pulseTimer = null; // Non-null while a pulse is being generated this.pulseTimer = null; // Non-null while a pulse is being generated
var node = this; var node = this;
// Generate a pulse in response to an input message. If the topic includes the text // 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 // 'time' (case insensitive) and the payload is numeric, use this value as the
// pulse time. Otherwise use the value from the properties dialog. // pulse time. Otherwise use the value from the properties dialog.
@ -444,14 +438,14 @@ function PulseOutputNode(n) {
} }
} }
}; };
// At the end of the pulse, restore the default state and set the timer to null // At the end of the pulse, restore the default state and set the timer to null
var endPulseCallback = function () { var endPulseCallback = function () {
node.pulseTimer = null; node.pulseTimer = null;
bonescript.digitalWrite(node.pin, node.defaultState); bonescript.digitalWrite(node.pin, node.defaultState);
node.send({ topic:node.topic, payload: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 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", 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", "P8_16", "P8_17", "P8_18", "P8_19", "P8_26", "P9_11", "P9_12", "P9_13", "P9_14",

View File

@ -52,10 +52,11 @@
</script> </script>
<script type="text/x-red" data-help-name="rpi-piface in"> <script type="text/x-red" data-help-name="rpi-piface in">
<p>Raspberry Pi PiFace input node. Generates a <b>msg.payload</b> with either a 0 or 1 depending on the state of the input pin. Requires the gpio command to work.</p> <p>Raspberry Pi PiFace input node. Generates a <b>msg.payload</b> with either a 0 or 1 depending on the state of the input pin.</p>
<p>You may also enable the input pullup resitor if required.</p> <p>You may also enable the input pullup resistor if required.</p>
<p>The <b>msg.topic</b> is set to <i>pi/{the pin number}</i></p> <p>The <b>msg.topic</b> is set to <i>piface/{the pin number}</i></p>
<p><b>Note:</b> This node currently polls the pin every 250mS. This is not ideal as it loads the cpu, and will be rewritten shortly to try to use interrupts.</p> <p>Requires the WiringPi gpio command in order to work.</p>
<p><b>Note:</b> This node currently polls the pin every 250mS. This is not ideal as it loads the cpu.</p>
</script> </script>
<script type="text/javascript"> <script type="text/javascript">
@ -103,9 +104,9 @@
</script> </script>
<script type="text/x-red" data-help-name="rpi-piface out"> <script type="text/x-red" data-help-name="rpi-piface out">
<p>Raspberry Pi PiFace output node. The PiFace board must be fitted. Requires the gpio command to work.</p> <p>Raspberry Pi PiFace output node. The PiFace board must be fitted.</p>
<p>Expects a <b>msg.payload</b> with either a 0 or 1 (or true or false).</p> <p>Will set the selected relay, LED, or pin on or off depending on the value passed in. Expects a <b>msg.payload</b> with either a 1 or 0 (or true or false).</p>
<p>Will set the selected relay, LED, or pin on or off depending on the value passed in.</p> <p>Requires the WiringPi gpio command in order to work.</p>
</script> </script>
<script type="text/javascript"> <script type="text/javascript">

View File

@ -57,6 +57,33 @@ var pintable = {
"LED 6":"206", "LED 6":"206",
"LED 7":"207" "LED 7":"207"
} }
var tablepin = {
// WiringPi : Physical
"200":"S1",
"201":"S2",
"202":"S3",
"203":"S4",
"204":"I5",
"205":"I6",
"206":"I7",
"207":"I8",
"208":"O0",
"209":"O1",
"210":"O2",
"211":"O3",
"212":"O4",
"213":"O5",
"214":"O6",
"215":"O7",
"200":"L0",
"201":"L1",
"202":"L2",
"203":"L3",
"204":"L4",
"205":"L5",
"206":"L6",
"207":"L7"
}
function PiFACEInNode(n) { function PiFACEInNode(n) {
RED.nodes.createNode(this,n); RED.nodes.createNode(this,n);
@ -76,7 +103,7 @@ function PiFACEInNode(n) {
var previousState = node.buttonState; var previousState = node.buttonState;
node.buttonState = Number(stdout); node.buttonState = Number(stdout);
if (previousState !== -1) { if (previousState !== -1) {
var msg = {topic:"pi/"+node.pin, payload:node.buttonState}; var msg = {topic:"piface/"+tablepin[node.pin], payload:node.buttonState};
node.send(msg); node.send(msg);
} }
} }

View File

@ -0,0 +1,111 @@
<!--
Copyright 2014 IBM Corp.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/x-red" data-template-name="rpi-pibrella in">
<div class="form-row">
<label for="node-input-pin"><i class="icon-asterisk"></i> Input</label>
<select type="text" id="node-input-pin" style="width: 150px;">
<option value="-">select input</option>
<option value="Red Button">Red Button</option>
<option value="In A">In A</option>
<option value="In B">In B</option>
<option value="In C">In C</option>
<option value="In D">In D</option>
</select>
</div>
<div class="form-row">
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
</script>
<script type="text/x-red" data-help-name="rpi-pibrella in">
<p>Raspberry Pi Pibrella input node. Generates a <b>msg.payload</b> with either a 0 or 1 depending on the state of the input pin.</p>
<p>The <b>msg.topic</b> is set to <i>pibrella/{the pin id}</i>, A, B, C, D or R</p>
<p><b>Note:</b> This node currently polls the pin every 250mS. This is not ideal as it loads the cpu.</p>
<p>Requires the WiringPi gpio command in order to work.</p>
</script>
<script type="text/javascript">
RED.nodes.registerType('rpi-pibrella in',{
category: 'advanced-input',
color:"#c6dbef",
defaults: {
name: { value:"" },
pin: { value:"",required:true,validate:RED.validators.regex(/ /) }
},
inputs:0,
outputs:1,
icon: "rpi.png",
label: function() {
return this.name||this.pin||"Pibrella";
},
labelStyle: function() {
return this.name?"node_label_italic":"";
}
});
</script>
<script type="text/x-red" data-template-name="rpi-pibrella out">
<div class="form-row">
<label for="node-input-pin"><i class="icon-asterisk"></i> Output</label>
<select type="text" id="node-input-pin" style="width: 150px;">
<option value="-">select output</option>
<option value="Red LED">Red LED</option>
<option value="Amber LED">Amber LED</option>
<option value="Green LED">Green LED</option>
<option value="Out E">Out E</option>
<option value="Out F">Out F</option>
<option value="Out G">Out G</option>
<option value="Out H">Out H</option>
<option value="Buzzer ">Buzzer</option>
</select>
</div>
<div class="form-row">
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
<div class="form-tips">Buzzer takes <b>msg.payload</b> between 2 (high) and 512 (low), or 0 for off.</div>
</script>
<script type="text/x-red" data-help-name="rpi-pibrella out">
<p>Raspberry Pi Pibrella output node. The Pibrella board must be fitted.</p>
<p>Will set the selected output high (on) or low (off) depending on the value passed in. Expects a <b>msg.payload</b> with either a 0 or 1 (or true or false).</p>
<p>The Buzzer is a divider so low numbers are high notes. 0 is off, and the sensible lowest note is around 250-300. 2 is the highest note. 1 is just a buzz (so you can use 0/1 type inputs).</p>
<p>Requires the WiringPi gpio command in order to work.</p>
</script>
<script type="text/javascript">
RED.nodes.registerType('rpi-pibrella out',{
category: 'advanced-output',
color:"#c6dbef",
defaults: {
name: { value:"" },
pin: { value:"",required:true,validate:RED.validators.regex(/ /) }
},
inputs:1,
outputs:0,
icon: "rpi.png",
align: "right",
label: function() {
return this.name||this.pin||"Pibrella";
},
labelStyle: function() {
return this.name?"node_label_italic":"";
}
});
</script>

View File

@ -0,0 +1,172 @@
/**
* Copyright 2014 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
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("/dev/ttyAMA0")) { // unlikely if not on a Pi
throw "Info : Ignoring Raspberry Pi specific node.";
}
if (!fs.existsSync("/usr/local/bin/gpio")) { // gpio command not installed
throw "Info : Can't find Raspberry Pi wiringPi gpio command.";
}
// 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",
"1":"Buzzer",
"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];
var node = this;
if (this.pin) {
exec("gpio mode "+node.pin+" in", function(err,stdout,stderr) {
if (err) node.error(err);
else {
node._interval = setInterval( function() {
exec("gpio read "+node.pin, function(err,stdout,stderr) {
if (err) node.error(err);
else {
if (node.buttonState !== Number(stdout)) {
var previousState = node.buttonState;
node.buttonState = Number(stdout);
if (previousState !== -1) {
var msg = {topic:"pibrella/"+tablepin[node.pin], payload:node.buttonState};
node.send(msg);
}
}
}
});
}, 200);
}
});
}
else {
this.error("Invalid GPIO pin: "+this.pin);
}
this.on("close", function() {
clearInterval(this._interval);
});
}
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) { // fixed buzz
exec("gpio pwm 1 511");
exec("gpio pwmc 100");
}
else if ((out >= 2) && (out <= 9999)) { // set buzz to a value
exec("gpio pwm 1 511");
exec("gpio pwmc "+out);
}
else { exec("gpio pwm 1 0"); } // turn it off
});
});
}
else if (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);
}
this.on("close", function() {
exec("gpio mode "+this.pin+" in");
});
}
exec("gpio mode 0 out",function(err,stdout,stderr) {
if (err) {
util.log('[36-rpi-gpio.js] Error: "gpio" command failed for some reason.');
}
exec("gpio mode 1 out");
exec("gpio mode 2 out");
exec("gpio mode 3 out");
exec("gpio mode 4 out");
exec("gpio mode 5 out");
exec("gpio mode 6 out");
exec("gpio mode 7 out");
exec("gpio mode 10 in");
exec("gpio mode 11 in");
exec("gpio mode 12 in");
exec("gpio mode 13 in");
exec("gpio mode 14 in");
});
RED.nodes.registerType("rpi-pibrella in",PibrellaIn);
RED.nodes.registerType("rpi-pibrella out",PibrellaOut);

View File

@ -27,6 +27,8 @@
<p>See <i><a href="http://www.piborg.com/ledborg/install" target="_new">the PiBorg site</a></i> for more information.</p> <p>See <i><a href="http://www.piborg.com/ledborg/install" target="_new">the PiBorg site</a></i> for more information.</p>
<p>You can also now use a <b>msg.payload</b> in the standard hex format "#rrggbb". The clip levels are :</p> <p>You can also now use a <b>msg.payload</b> in the standard hex format "#rrggbb". The clip levels are :</p>
<p><pre>0x00 - 0x57 = off<br/>0x58 - 0xA7 = 50%<br/>0xA8 - 0xFF = fully on</pre></p> <p><pre>0x00 - 0x57 = off<br/>0x58 - 0xA7 = 50%<br/>0xA8 - 0xFF = fully on</pre></p>
<p>You can also use the @cheerlight colour names - red, amber, green, blue, cyan, magenta, yeloow, orange, pink, purple,
white, warmwhite, black</p>
</script> </script>
<script type="text/javascript"> <script type="text/javascript">

View File

@ -20,8 +20,7 @@ var fs = require('fs');
// check if /dev/ledborg exists - if not then don't even show the node. // check if /dev/ledborg exists - if not then don't even show the node.
if (!fs.existsSync("/dev/ledborg")) { if (!fs.existsSync("/dev/ledborg")) {
util.log("[78-ledborg.js] Warning: PiBorg hardware : LedBorg not found"); throw "Info : PiBorg hardware : LedBorg not found";
return;
} }
function LedBorgNode(n) { function LedBorgNode(n) {

View File

@ -13,6 +13,7 @@
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
--> -->
<script type="text/x-red" data-template-name="Discover"> <script type="text/x-red" data-template-name="Discover">
@ -28,27 +29,25 @@
<!-- Next, some simple help text is provided for the node. --> <!-- Next, some simple help text is provided for the node. -->
<script type="text/x-red" data-help-name="Discover"> <script type="text/x-red" data-help-name="Discover">
<p>This node looks for a Philips Hue Bridge in the local network.</p> <p>This node looks for a Philips Hue Bridge in the local network.</p><p> The node has 2 outputs, the first one contains the IP address of the first discovered bridge and the second one the lights registered to that bridge (in JSON format).</p> <p>To use the node you need to have obtained a valid auth token (or username) from your Philips Hue Bridge. Read <a href="http://developers.meethue.com/gettingstarted.html" target="_blank">here</a> on how to do this.</p>
<p> The node has 2 outputs, the first one contains the IP address of the first discovered bridge and the second one the lights registered to that bridge (in JSON format).</p>
<p>To use the node you need to have obtained a valid auth token (or username) from your Philips Hue Bridge. Read <a href="http://developers.meethue.com/gettingstarted.html" target="_blank">here</a> on how to do this.</p>
</script> </script>
<!-- Finally, the node type is registered along with all of its properties --> <!-- Finally, the node type is registered along with all of its properties -->
<script type="text/javascript"> <script type="text/javascript">
RED.nodes.registerType('Discover',{ RED.nodes.registerType('Discover',{
category: 'advanced-input', category: 'advanced-input', // the palette category
color:"#EFEFEF", color:"#EFEFEF",
defaults: { defaults: { // defines the editable properties of the node
name: {value:""}, name: {value:""}, // along with default values.
username: {value:"", required:true} username: {value:"", required:true}
}, },
inputs:1, inputs:1, // set the number of inputs - only 0 or 1
outputs:2, outputs:2, // set the number of outputs - 0 to n
icon: "hue.png", icon: "huediscover.png", // set the icon (held in public/icons)
label: function() { label: function() { // sets the default label contents
return this.name||"Discover"; return this.name||this.topic||"Discover";
}, },
labelStyle: function() { labelStyle: function() { // sets the class to apply to the label
return this.name?"node_label_italic":""; return this.name?"node_label_italic":"";
} }
}); });

View File

@ -13,6 +13,7 @@
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
--> -->
<script type="text/x-red" data-template-name="HueNode"> <script type="text/x-red" data-template-name="HueNode">
@ -35,6 +36,11 @@
</select> </select>
</div> </div>
<div class="form-row">
<label for="node-input-brightness"><i class="icon-tag"></i>Change Brightness (0->100):</label>
<input type="text" id="node-input-brightness" placeholder="brightness">
</div>
<div class="form-row"> <div class="form-row">
<label for="node-input-color"><i class="icon-tag"></i>Select color:</label> <label for="node-input-color"><i class="icon-tag"></i>Select color:</label>
<input type="text" id="node-input-color" placeholder="color"> <input type="text" id="node-input-color" placeholder="color">
@ -50,29 +56,38 @@
<script type="text/x-red" data-help-name="HueNode"> <script type="text/x-red" data-help-name="HueNode">
<p>This node implements some basic functionality for managing a Philips Hue wireless Lamp system.</p> <p>This node implements some basic functionality for managing a Philips Hue wireless Lamp system.</p>
<p>To use it you need to have obtained a valid auth token (or username) from your Philips Hue Bridge. Read <a href="http://developers.meethue.com/gettingstarted.html" target="_blank">here</a> on how to do this.</p> <p>To use it you need to have obtained a valid auth token (or username) from your Philips Hue Bridge. Read <a href="http://developers.meethue.com/gettingstarted.html" target="_blank">here</a> on how to do this.</p>
<p>You can enter the ID (1, 2, ...) of a Lamp and turn it ON or OFF and also set its color. </p><p>By setting the status to AUTO, you can set the ON/OFF status as a message payload (e.g., msg.payload="ON") and the color through the message topic (e.g., msg.topic="EBF5FF") on the node input. Please note, in case you use both, the status selection overides the msg.payload!</p><p>Also, if you pass something like msg.payload="ALERT" the Lamp will flash once.</p> <p>You can enter the ID (1, 2, ...) of a Lamp and turn it ON or OFF, set the color and the brightness (0->100). </p><p>By setting the status to AUTO, you can set the lamp parameters using the message on the input node as follows:</p>
<ul>
<li>msg.lamp sets the lamp ID</li>
<li>msg.color sets the lamp color (e.g., msg.color="DF0101" will set the color to red)</li>
<li>msg.brightness sets the lamp brightness (e.g., msg.brightness=50)</li>
<li>msg.payload is used to se the lamp status (on/off/alert) (e.g., msg.payload="alert" will flash the Lamp once</li>
</ul>
<p>Please note, by setting the status to AUTO on the node configuration, the rest of the node parameters are ignored, you need to set all parameters through the message input.</p>
</script> </script>
<!-- Finally, the node type is registered along with all of its properties --> <!-- Finally, the node type is registered along with all of its properties -->
<script type="text/javascript"> <script type="text/javascript">
RED.nodes.registerType('HueNode',{ RED.nodes.registerType('HueNode',{
category: 'advanced-input', category: 'advanced-input', // the palette category
color:"#EFEFEF", color:"#EFEFEF",
defaults: { defaults: { // defines the editable properties of the node
name: {value:""}, name: {value:""}, // along with default values.
username: {value:"", required:true}, username: {value:"", required:true},
discovery_mode: {value: "", required:false}, discovery_mode: {value: "", required:false},
lamp_id: {value:"", required:false}, lamp_id: {value:"", required:false},
color: {value:"EBF5FF"}, color: {value:"EBF5FF"},
brightness: {value:"100"},
lamp_status:{} lamp_status:{}
}, },
inputs:1, inputs:1, // set the number of inputs - only 0 or 1
outputs:1, outputs:1, // set the number of outputs - 0 to n
icon: "hue.png", icon: "huemanage.png", // set the icon (held in public/icons)
label: function() { label: function() { // sets the default label contents
return this.name||"HueNode"; return this.name||this.topic||"HueNode";
}, },
labelStyle: function() { labelStyle: function() { // sets the class to apply to the label
return this.name?"node_label_italic":""; return this.name?"node_label_italic":"";
} }
}); });

View File

@ -18,14 +18,19 @@
* limitations under the License. * limitations under the License.
**/ **/
//Require node-hue-api //Require node-hue-api
var hue = require("node-hue-api"); var hue = require("node-hue-api");
var HueApi = require("node-hue-api").HueApi; var HueApi = require("node-hue-api").HueApi;
// Require main module
var RED = require(process.env.NODE_RED_HOME+"/red/red"); var RED = require(process.env.NODE_RED_HOME+"/red/red");
//store the IP address of the Hue Gateway //store the IP address of the Hue Gateway
var gw_ipaddress = ""; var gw_ipaddress = "";
var username, lamp_status, lamp_id, color;
var username, lamp_status, lamp_id, color, brightness;
function hexToRgb(hex) { function hexToRgb(hex) {
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
@ -36,74 +41,113 @@ function hexToRgb(hex) {
} : null; } : null;
} }
// The main node definition - most things happen in here // The main node definition - most things happen in here
function HueNode(n) { function HueNode(n) {
// Create a RED node // Create a RED node
RED.nodes.createNode(this,n); RED.nodes.createNode(this,n);
var node = this;
//get parameters from user //get parameters from user
this.username = n.username; this.username = n.username;
this.lamp_status = n.lamp_status; this.lamp_status = n.lamp_status;
this.lamp_id = n.lamp_id; this.lamp_id = n.lamp_id;
this.color = n.color; this.color = n.color;
var node = this; this.brightness = n.brightness;
// Store local copies of the node configuration (as defined in the .html)
this.topic = n.topic;
var msg = {}; var msg = {};
msg.topic = this.topic;
this.on("input", function(msg){ this.on("input", function(msg){
//set the lamp status var myMsg = msg;
//first locate the Hue gateway: //set the lamp status
hue.locateBridges(function(err, result) { //first locate the Hue gateway:
hue.locateBridges(function(err, result) {
var msg2 = {}; var msg2 = {};
msg2.topic = this.topic; msg2.topic = this.topic;
if (err) throw err; if (err) throw err;
//check for found bridges //check for found bridges
if(result[0]!=null) { if(result[0]!=null) {
//save the IP address of the 1st bridge found //save the IP address of the 1st bridge found
this.gw_ipaddress = result[0].ipaddress; this.gw_ipaddress = result[0].ipaddress;
//set light status //set light status
var api = new HueApi(this.gw_ipaddress, node.username); var api = new HueApi(this.gw_ipaddress, node.username);
var lightState = hue.lightState; var lightState = hue.lightState;
var state = lightState.create(); var state = lightState.create();
var status; var status;
if(msg.payload=="ALERT"){ var lamp = -1;
status = "ALERT";
} //check for AUTO status (lamp settings set through node input)
else if(node.lamp_status=="ON" || msg.payload=="ON") status = "ON"; if(node.lamp_status=="AUTO") {
else if(node.lamp_status=="OFF" || msg.payload=="OFF") status = "OFF"; var color;
var brightness;
//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;
}
if(status=="ALERT") { //check the payload for on/off/alert:
api.setLightState(node.lamp_id, state.alert()).then(displayResult).fail(displayError).done(); //case of ALERT:
} if(myMsg.payload=="ALERT" || myMsg.payload=="alert"){
else if(status=="ON") { api.setLightState(lamp, state.alert()).then(displayResult).fail(displayError).done();
if(node.color==null) { }
api.setLightState(node.lamp_id, state.on().rgb(hexToRgb(msg.topic).r,hexToRgb(msg.topic).g,hexToRgb(msg.topic).b)).then(displayResult).fail(displayError).done();
//case of 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 {
api.setLightState(lamp, state.off()).then(displayResult).fail(displayError).done();
}
} }
else { else {
api.setLightState(node.lamp_id, state.on().rgb(hexToRgb(node.color).r,hexToRgb(node.color).g,hexToRgb(node.color).b)).then(displayResult).fail(displayError).done(); //set lamp according to node settings
if(node.lamp_status=="ON")
api.setLightState(node.lamp_id, state.on().rgb(hexToRgb(node.color).r,hexToRgb(node.color).g,hexToRgb(node.color).b).brightness(node.brightness)).then(displayResult).fail(displayError).done();
else
api.setLightState(node.lamp_id, state.off()).then(displayResult).fail(displayError).done();
} }
if(lamp!=-1)
msg2.payload = 'Light with ID: '+lamp+ ' was set to '+myMsg.payload;
else
msg2.payload = 'Light with ID: '+node.lamp_id+ ' was set to '+node.lamp_status;
node.send(msg2);
} }
else { else {
api.setLightState(node.lamp_id, state.off()).then(displayResult).fail(displayError).done(); //bridge not found:
var msg = {};
msg.payload = "Bridge not found!";
node.send(msg);
} }
msg2.payload = 'Light with ID: '+node.lamp_id+ ' was set to '+status; });
node.send(msg2);
}
else {
//bridge not found:
var msg = {};
msg.payload = "Bridge not found!";
node.send(msg);
}
});
}); });
this.on("close", function() { this.on("close", function() {
// Called when the node is shutdown - eg on redeploy. // Called when the node is shutdown - eg on redeploy.
// Allows ports to be closed, connections dropped etc. // Allows ports to be closed, connections dropped etc.
@ -121,6 +165,9 @@ var displayError = function(err) {
console.error(err); console.error(err);
}; };
// Register the node by name. This must be called before overriding any of the // Register the node by name. This must be called before overriding any of the
// Node functions. // Node functions.
RED.nodes.registerType("HueNode",HueNode); RED.nodes.registerType("HueNode",HueNode);

View File

@ -118,7 +118,7 @@ function enable(node) {
} else { } else {
node.stag.unnotifyHumidity(function() {}); node.stag.unnotifyHumidity(function() {});
} }
if (node.accelometer){ if (node.accelerometer){
node.stag.notifyAccelerometer(function() {}); node.stag.notifyAccelerometer(function() {});
} else { } else {
node.stag.unnotifyAccelerometer(function() {}); node.stag.unnotifyAccelerometer(function() {});

View File

@ -21,8 +21,8 @@ var fs = require('fs');
var plat = require('os').platform(); var plat = require('os').platform();
var pre = "\\\\.\\"; var pre = "\\\\.\\";
if (! plat.match(/^win/)) { if (!plat.match(/^win/)) {
util.log("[26-rawserial.js] Advise: Only really needed for Windows boxes without serialport npm module installed."); util.log("[26-rawserial.js] Info : only really needed for Windows boxes without serialport npm module installed.");
pre = ""; pre = "";
} }

View File

@ -26,10 +26,10 @@
</script> </script>
<script type="text/x-red" data-help-name="notify"> <script type="text/x-red" data-help-name="notify">
<p>Uses Growl to provide a desktop popup containing the <b>msg.payload</b>. Only useful on the local machine.</p> <p>Uses Growl to provide a desktop popup containing the <b>msg.payload</b>. Only useful on the local machine.</p>
<p>Optionally uses <b>msg.topic</b> as the title.</p> <p>Optionally uses <b>msg.topic</b> as the title.</p>
<p>Uses Growl so should work cross platform but will need pre-reqs installed... see <i><a href="https://npmjs.org/package/growl" target="_new">this link.</a></i></p> <p>Uses Growl so should work cross platform but will need pre-reqs installed... see <i><a href="https://npmjs.org/package/growl" target="_new">this link.</a></i></p>
<p>If installing on Windows you MUST read the install instructions ... especially the bit about adding growlnotify to your path... or it WON'T work.</p> <p>If installing on Windows you MUST read the install instructions ... especially the bit about adding growlnotify to your path... or it WILL NOT work.</p>
</script> </script>
<script type="text/javascript"> <script type="text/javascript">

View File

@ -16,24 +16,24 @@
var RED = require(process.env.NODE_RED_HOME+"/red/red"); var RED = require(process.env.NODE_RED_HOME+"/red/red");
var growl = require('growl'); var growl = require('growl');
var imagefile = process.env.NODE_RED_HOME+"/public/mqtt-node-red.png"; var imagefile = process.env.NODE_RED_HOME+"/public/node-red.png";
function NotifyNode(n) { function NotifyNode(n) {
RED.nodes.createNode(this,n); RED.nodes.createNode(this,n);
this.title = n.title; this.title = n.title;
var node = this; var node = this;
this.on("input",function(msg) { this.on("input",function(msg) {
var titl = this.title||msg.topic; var titl = this.title || msg.topic;
if (typeof(msg.payload) == 'object') { if (typeof(msg.payload) == 'object') {
msg.payload = JSON.stringify(msg.payload); msg.payload = JSON.stringify(msg.payload);
} }
if (typeof(titl) != 'undefined') { if (typeof(titl) != 'undefined') {
growl(msg.payload, { title: titl, image: imagefile }); growl(msg.payload, { title: titl, image: imagefile });
} }
else { else {
growl(msg.payload, { image: imagefile }); growl(msg.payload, { image: imagefile });
} }
}); });
} }
RED.nodes.registerType("notify",NotifyNode); RED.nodes.registerType("notify",NotifyNode);

View File

@ -16,6 +16,17 @@
--> -->
<script type="text/x-red" data-template-name="twilio out"> <script type="text/x-red" data-template-name="twilio out">
<div class="form-row" id="node-input-credentials-row">
<label for="node-input-creds"><i class="icon-folder-close"></i> Credentials</label>
<select id="node-input-creds">
<option value="global">Use global credentials</option>
<option value="local">Use local credentials</option>
</select>
</div>
<div class="form-row" id="node-input-twilio-row">
<label for="node-input-twilio"><i class="icon-user"></i> Twilio</label>
<input type="text" id="node-input-twilio">
</div>
<div class="form-row"> <div class="form-row">
<label for="node-input-number"><i class="icon-envelope"></i> SMS to</label> <label for="node-input-number"><i class="icon-envelope"></i> SMS to</label>
<input type="text" id="node-input-number" placeholder="01234 5678901"> <input type="text" id="node-input-number" placeholder="01234 5678901">
@ -24,36 +35,136 @@
<label for="node-input-name"><i class="icon-tag"></i> Name</label> <label for="node-input-name"><i class="icon-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name"> <input type="text" id="node-input-name" placeholder="Name">
</div> </div>
<div class="form-tips">Tip - leave Number blank to use <b>msg.topic</b> to set the number.</div>
</script> </script>
<script type="text/x-red" data-help-name="twilio out"> <script type="text/x-red" data-help-name="twilio out">
<p>Uses Twilio to send the <b>msg.payload</b> as a SMS to the configured number.</p> <p>Sends an SMS message using the Twilio service.</p>
<p>Uses <b>msg.topic</b> to set the phone number, if not already set in the properties.</p> <p><code>msg.payload</code> is used as the body of the message. The node can be configured with the number
<p>You MUST configure both your Account SID and the Auth Token. Either into settings.js like this</p> to send the message to. Alternatively, if the number is left blank, it can be set using <code>msg.topic</code>.</p>
<p><pre>twilio: { account:'My-ACCOUNT-SID', authtoken:'TWILIO-TOKEN', from:'FROM-NUMBER' },</pre></p> <p>You must have an account with Twilio to use this node. You can register for one <a href="https://www.twilio.com/">here</a>.</p>
<p>Or as a twiliokey.js file in the directory <b>above</b> node-red.<p> <p>You can either set your account details within the node, or provide it globally using either the settings file or a file
<p><pre>module.exports = { account:'My-ACCOUNT-SID', authtoken:'TWILIO-TOKEN',from:'FROM-NUMBER' }</pre></p> called 'twiliokey.js' located in the directory above node-red.</p>
<p>To use the settings.js file, add an entry such as:
<pre>twilio: { account:'My-ACCOUNT-SID', authtoken:'TWILIO-TOKEN', from:'FROM-NUMBER' }</pre></p>
<p>To use the 'twiliokey.js' file in the directory <b>above</b> node-red, use the following format:
<pre>module.exports = { account:'My-ACCOUNT-SID', authtoken:'TWILIO-TOKEN',from:'FROM-NUMBER' }</pre></p>
</script>
<script type="text/x-red" data-template-name="twilio-api">
<div class="form-row">
<label for="node-config-input-sid">Account SID</label>
<input type="text" id="node-config-input-sid">
</div>
<div class="form-row">
<label for="node-config-input-from"><i class="icon-envelope"></i> From</label>
<input type="text" id="node-config-input-from" placeholder="01234 5678901">
</div>
<div class="form-row">
<label for="node-config-input-token"><i class="icon-lock"></i> Token</label>
<input type="password" id="node-config-input-token">
</div>
<div class="form-row">
<label for="node-config-input-name"><i class="icon-tag"></i> Name</label>
<input type="text" id="node-config-input-name" placeholder="Name">
</div>
</script> </script>
<script type="text/javascript"> <script type="text/javascript">
RED.nodes.registerType('twilio out',{
category: 'output',
defaults: { (function() {
number: {value:""},
name: {value:""} var hasGlobal = false;
}, $.getJSON('twilio-api/global',function(data) {
color:"#ed1c24", hasGlobal = data.hasToken;
inputs:1, });
outputs:0,
icon: "twilio.png",
align: "right", RED.nodes.registerType('twilio-api',{
label: function() { category: 'config',
return this.name||this.title||"twilio"; defaults: {
}, sid: {value:"",required:true},
labelStyle: function() { from: {value:"",required:true},
return this.name?"node_label_italic":""; // token -> credentials
} name: { value: ""}
}); },
label: function() {
return this.name||this.from;
},
oneditprepare: function() {
$.getJSON('twilio-api/'+this.id,function(data) {
if (data.hasToken) {
$('#node-config-input-token').val('__PWRD__');
} else {
$('#node-config-input-token').val('');
}
});
},
oneditsave: function() {
var newToken = $('#node-config-input-token').val();
if (newToken != '__PWRD__') {
var credentials = {};
credentials.token = newToken;
$.ajax({
url: 'twilio-api/'+this.id,
type: 'POST',
data: credentials,
success:function(result){}
});
}
},
ondelete: function() {
$.ajax({
url: 'twilio-api/'+this.id,
type: 'DELETE',
success: function(result) {}
});
}
});
RED.nodes.registerType('twilio out',{
category: 'output',
defaults: {
twilio:{type:"twilio-api",validate:function(v) {
return hasGlobal || (v && v!="_ADD_");
}},
number: {value:""},
name: {value:""}
},
color:"#FF595F",
inputs:1,
outputs:0,
icon: "twilio.png",
align: "right",
label: function() {
return this.name||this.title||"twilio";
},
labelStyle: function() {
return this.name?"node_label_italic":"";
},
oneditprepare: function() {
if (hasGlobal) {
$("#node-input-creds").change(function() {
var val = $(this).val();
if (val == "global") {
$("#node-input-twilio-row").hide();
} else {
$("#node-input-twilio-row").show();
}
});
$("#node-input-credentials-row").show();
if (!this.twilio) {
$("#node-input-creds").val("global");
} else {
$("#node-input-creds").val("local");
}
$("#node-input-creds").change();
} else {
$("#node-input-credentials-row").hide();
}
}
});
})();
</script> </script>

View File

@ -17,47 +17,97 @@
var RED = require(process.env.NODE_RED_HOME+"/red/red"); var RED = require(process.env.NODE_RED_HOME+"/red/red");
var util = require('util'); var util = require('util');
var twilio = require('twilio');
// 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' }
try { try {
var twiliokey = RED.settings.twilio || require(process.env.NODE_RED_HOME+"/../twiliokey.js"); var twiliokey = RED.settings.twilio || require(process.env.NODE_RED_HOME+"/../twiliokey.js");
} }
catch(err) { catch(err) {
util.log("[56-twilio.js] Error: Failed to load Twilio credentials");
} }
if (twiliokey) { var querystring = require('querystring');
var twilioClient = require('twilio')(twiliokey.account, twiliokey.authtoken);
var fromNumber = twiliokey.from;
}
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) { function TwilioOutNode(n) {
RED.nodes.createNode(this,n); RED.nodes.createNode(this,n);
this.number = n.number; 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; var node = this;
this.on("input",function(msg) { this.on("input",function(msg) {
if (typeof(msg.payload) == 'object') { if (typeof(msg.payload) == 'object') {
msg.payload = JSON.stringify(msg.payload); msg.payload = JSON.stringify(msg.payload);
} }
if (twiliokey) { try {
try { // Send SMS
// Send SMS var tonum = node.number || msg.topic;
var tonum = node.number || msg.topic; node.twilioClient.sendMessage( {to: tonum, from: node.fromNumber, body: msg.payload}, function(err, response) {
twilioClient.sendMessage( {to: tonum, from: fromNumber, body: msg.payload}, function(err, response) { if (err) {
if (err) node.error(err); node.error(err);
//console.log(response); }
}); //console.log(response);
} });
catch (err) { } catch (err) {
node.error(err); node.error(err);
}
}
else {
node.warn("Twilio credentials not set/found. See node info.");
} }
}); });
} }

View File

@ -38,7 +38,7 @@ function SunNode(n) {
var mins2 = times[node.end].getUTCMinutes(); var mins2 = times[node.end].getUTCMinutes();
var e1 = (hour*60+mins) - (hour1*60+mins1); var e1 = (hour*60+mins) - (hour1*60+mins1);
var e2 = (hour*60+mins) - (hour2*60+mins2); 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 }; msg = { payload:0, topic:"sun", moon:moon };
if ((e1 > 0) & (e2 < 0)) { msg.payload = 1; } if ((e1 > 0) & (e2 < 0)) { msg.payload = 1; }
if (oldval == null) { oldval = msg.payload; } if (oldval == null) { oldval = msg.payload; }