Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Ben Hardill 2013-10-13 17:33:19 +01:00
commit 74d8958526
30 changed files with 560 additions and 193 deletions

View File

@ -2,6 +2,8 @@
A visual tool for wiring the Internet of Things.
![Screenshot](http://nodered.org/images/node-red-screenshot.png "Node-RED: A visual tool for wiring the Internet of Things")
## Quick Start
Check out [INSTALL](INSTALL.md) for full instructions on getting started.
@ -16,6 +18,8 @@ Check out [INSTALL](INSTALL.md) for full instructions on getting started.
More documentation can be found [here](http://nodered.org/docs).
For further help, or general discussion, there is also a [mailing list](https://groups.google.com/forum/#!forum/node-red).
## Browser Support
The Node-RED editor runs in the browser. We routinely develop and test using
@ -28,9 +32,11 @@ list.
### Reporting issues
Please raise any bug reports or feature requests on the project's issue
tracker. Be sure to search the list to see if your issue has already
been raised.
Please raise any bug reports on the project's [issue tracker](https://github.com/node-red/node-red/issues?state=open).
Be sure to search the list to see if your issue has already been raised.
For feature requests, please raise them on the [mailing list](https://groups.google.com/forum/#!forum/node-red)
first.
### Creating new nodes

View File

@ -17,6 +17,8 @@
<!-- Sample html file that corresponds to the 99-sample.js file -->
<!-- This creates and configures the onscreen elements of the node -->
<!-- If you use this as a template, replace IBM Corp. with your own name. -->
<!-- First, the content of the edit dialog is defined. -->
<script type="text/x-red" data-template-name="sample">
@ -24,7 +26,7 @@
<!-- Each of the following divs creates a field in the edit dialog. -->
<!-- Generally, there should be an input for each property of the node. -->
<!-- The for and id attributes identify the corresponding property -->
<!-- The for and id attributes identify the corresponding property -->
<!-- (with the 'node-input-' prefix). -->
<!-- The available icon classes are defined in Twitter Bootstrap -->
<div class="form-row">
@ -38,7 +40,6 @@
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
</script>

View File

@ -14,38 +14,39 @@
* limitations under the License.
**/
// If you use this as a template, replace IBM Corp. with your own name.
// Sample Node-RED node file
// Require main module
var RED = require("../../red/red");
// The main node definition - most things happen in here
function SampleNode(n) {
function SampleNode(n) {
// Create a RED node
RED.nodes.createNode(this,n);
// Store local copies of the node configuration (as defined in the .html)
this.topic = n.topic;
// Do whatever you need to do in here - declare callbacks etc
// Note: this sample doesn't do anything much - it will only send
// this message once at startup...
// Look at other real nodes for some better ideas of what to do....
var msg = {};
msg.topic = node.topic;
msg.topic = this.topic;
msg.payload = "Hello world !"
// send out the message to the rest of the workspace.
this.send(msg);
this.on("close", function() {
// Called when the node is shutdown - eg on redeploy.
// Allows ports to be closed, connections dropped etc.
// eg: this.client.disconnect();
});
}
// Register the node by name. This must be called before overriding any of the
// Node functions.
RED.nodes.registerType("sample",SampleNode);
SampleNode.prototype.close = function() {
// Called when the node is shutdown - eg on redeploy.
// Allows ports to be closed, connections dropped etc.
// eg: this.client.disconnect();
}

View File

@ -37,7 +37,7 @@
category: 'advanced-function',
color:"#E6E0F8",
defaults: {
useEyes: {value:"false"},
useEyes: {value:false},
name: {value:""},
},
inputs:1,

View File

@ -202,7 +202,7 @@
},
oneditprepare: function() {
var repeattype = "none";
if (Number(this.repeat) != 0) {
if (this.repeat != "") {
repeattype = "interval";
$("#inject-time-interval-units option").filter(function() {return $(this).val() == "s";}).attr('selected',true);
$("#inject-time-interval-count").val(this.repeat);
@ -210,7 +210,7 @@
} else if (this.crontab) {
var cronparts = this.crontab.split(" ");
var days = cronparts[4];
if (Number(cronparts[0]) && Number(cronparts[1])) {
if (!isNaN(cronparts[0]) && !isNaN(cronparts[1])) {
repeattype = "time";
// Fixed time
var time = cronparts[1]+":"+cronparts[0];

View File

@ -21,7 +21,7 @@
</div>
<div class="form-row">
<label for="node-input-func"><i class="icon-wrench"></i> Function</label>
<input type="hidden" id="node-input-func">
<input type="hidden" id="node-input-func" autofocus="autofocus">
<div style="height: 250px;" class="node-text-editor" id="node-input-func-editor" ></div>
</div>
<div class="form-row">
@ -85,6 +85,7 @@
editor:that.editor, // the field name the main text body goes to
fields:['name','outputs']
});
$("#node-input-name").focus();
});
},

View File

@ -22,7 +22,7 @@
<div class="form-row">
<label for="node-input-template"><i class="icon-wrench"></i> Template</label>
<input type="hidden" id="node-input-template">
<input type="hidden" id="node-input-template" autofocus="autofocus">
<div style="height: 250px;" class="node-text-editor" id="node-input-template-editor" ></div>
</div>
@ -79,7 +79,7 @@
editor:that.editor, // the field name the main text body goes to
fields:['name']
});
$("#node-input-name").focus();
});
},
oneditsave: function() {

View File

@ -21,7 +21,7 @@
</div>
<div class="form-row">
<label for="node-input-info" style="width: 100% !important;"><i class="icon-file"></i> More</label>
<input type="hidden" id="node-input-info">
<input type="hidden" id="node-input-info" autofocus="autofocus">
<div style="height: 250px;" class="node-text-editor" id="node-input-info-editor" ></div>
</div>
<div class="form-tips">Tip: this isn't meant for War and Peace - but useful notes can be kept here.</div>
@ -75,6 +75,7 @@
showFoldingRuler:false,
contents: $("#node-input-info").val()
});
$("#node-input-name").focus();
});
},
oneditsave: function() {

View File

@ -138,7 +138,7 @@
category: 'config',
defaults: {
//baud: {baud:"57600",required:true},
repeat: {value:"25",required:true,validate:RED.validators.number()},
repeat: {value:"50",required:true,validate:RED.validators.number()},
device: {value:"",required:true}
},
label: function() {

View File

@ -25,46 +25,46 @@ function ArduinoNode(n) {
RED.nodes.createNode(this,n);
this.device = n.device;
this.repeat = n.repeat||25;
util.log("[firmata] Opening"+this.device);
util.log("[firmata] Opening "+this.device);
var node = this;
// var tou = setInterval(function() {
// if (!arduinoReady) {
// clearInterval(tou);
arduinoReady = false;
if (thisboard == null) {
this.board = new firmata.Board(this.device, function(err) {
if (err) {
util.log("[firmata] "+err);
return;
node.toun = setInterval(function() {
if (!arduinoReady) {
if (thisboard == null) {
node.board = new firmata.Board(node.device, function(err) {
if (err) {
console.log("[firmata] error: ",err);
return;
}
arduinoReady = true;
thisboard = node.board;
clearInterval(node.toun);
util.log('[firmata] Arduino connected');
});
}
arduinoReady = true;
util.log('[firmata] Arduino connected');
});
thisboard = this.board;
}
else {
util.log("[firmata] Arduino already connected");
this.board = thisboard;
console.log(this.board._events);
this.board.removeAllListeners();
arduinoReady = true;
}
else {
node.board = thisboard;
node.board.removeAllListeners();
arduinoReady = true;
clearInterval(node.toun);
node.toun = false;
util.log("[firmata] Arduino already connected");
}
} else { util.log("[firmata] Waiting for Firmata"); }
}, 10000); // wait for firmata to connect to arduino
// } else { util.log("[firmata] Waiting for Firmata"); }
// }, 1000); // wait for firmata to disconnect from arduino
this._close = function() {
this.on('close', function() {
//this.board.sp.close(function() { console.log("[firmata] Serial port closed"); arduinoReady = false; });
arduinoReady = false;
if (node.toun) {
clearInterval(node.toun);
util.log("[firmata] arduino wait loop stopped");
}
util.log("[firmata] Stopped");
}
});
}
RED.nodes.registerType("arduino-board",ArduinoNode);
ArduinoNode.prototype.close = function() {
this._close();
}
// The Input Node
function DuinoNodeIn(n) {
@ -78,11 +78,13 @@ function DuinoNodeIn(n) {
this.board = this.serverConfig.board;
this.repeat = this.serverConfig.repeat;
var node = this;
var tout = setInterval(function() {
if (arduinoReady) {
clearInterval(tout);
console.log(node.state,node.pin,node.board.MODES[node.state]);
node.toui = setInterval(function() {
if (thisboard != null) {
node.board = thisboard;
clearInterval(node.toui);
node.toui = false;
//console.log(node.state,node.pin,node.board.MODES[node.state]);
node.board.pinMode(node.pin, node.board.MODES[node.state]);
node.board.setSamplingInterval(node.repeat);
var oldrdg = "";
@ -103,23 +105,21 @@ function DuinoNodeIn(n) {
}
}
else { node.log("Waiting for Arduino"); }
}, 2000); // loop to wait for firmata to connect to arduino
this._close = function() {
clearInterval(this._interval);
util.log("[arduino] input eventlistener stopped");
}
}, 5000); // loop to wait for firmata to connect to arduino
this.on('close', function() {
if (node.toui) {
clearInterval(node.toui);
util.log("[firmata] input wait loop stopped");
}
});
}
else {
util.log("[arduino] Serial Port not Configured");
util.log("[firmata] Serial Port not Configured");
}
}
RED.nodes.registerType("arduino in",DuinoNodeIn);
DuinoNodeIn.prototype.close = function() {
this._close();
}
// The Output Node
function DuinoNodeOut(n) {
@ -132,10 +132,10 @@ function DuinoNodeOut(n) {
if (typeof this.serverConfig === "object") {
this.board = this.serverConfig.board;
var node = this;
this.on("input", function(msg) {
//console.log(msg);
if (arduinoReady) {
if (thisboard != null) {
if (node.state == "OUTPUT") {
if ((msg.payload == true)||(msg.payload == 1)||(msg.payload.toString().toLowerCase() == "on")) {
node.board.digitalWrite(node.pin, node.board.HIGH);
@ -161,17 +161,26 @@ function DuinoNodeOut(n) {
}
//else { console.log("Arduino not ready"); }
});
var touo = setInterval(function() {
if (arduinoReady) {
clearInterval(touo);
//console.log(node.state,node.pin,node.board.MODES[node.state]);
node.touo = setInterval(function() {
if (thisboard != null) {
clearInterval(node.touo);
node.touo = false;
node.board = thisboard;
node.board.pinMode(node.pin, node.board.MODES[node.state]);
}
else { util.log("[firmata] waiting for arduino to connect"); }
}, 5000); // loop to wait for firmata to connect to arduino
this.on('close', function() {
if (node.touo) {
clearInterval(node.touo);
util.log("[firmata] output wait loop stopped");
}
});
}
else {
util.log("[arduino] Serial Port not Configured");
util.log("[firmata] Serial Port not Configured");
}
}
RED.nodes.registerType("arduino out",DuinoNodeOut);

View File

@ -57,7 +57,7 @@ RED.nodes.registerType("http in",HTTPIn);
function HTTPOut(n) {
RED.nodes.createNode(this,n);
var node = this;
this.on("input",function(msg) {
if (msg.res) {
if (msg.headers) {
@ -65,6 +65,8 @@ function HTTPOut(n) {
}
var statusCode = msg.statusCode || 200;
msg.res.send(statusCode,msg.payload);
} else {
node.warn("No response object");
}
});
}
@ -80,9 +82,9 @@ function HTTPRequest(n) {
this.on("input",function(msg) {
var opts = urllib.parse(msg.url||url);
opts.method = msg.method||method;
opts.method = (msg.method||method).toUpperCase();
if (msg.headers) {
opts.header = headers;
opts.header = msg.headers;
}
var req = httplib.request(opts,function(res) {
res.setEncoding('utf8');
@ -103,7 +105,7 @@ function HTTPRequest(n) {
msg.statusCode = err.code;
node.send(msg);
});
if (msg.payload) {
if (msg.payload && (method == "PUSH" || method == "PUT") ) {
if (typeof msg.payload === "string" || Buffer.isBuffer(msg.payload)) {
req.write(msg.payload);
} else if (typeof msg.payload == "number") {

View File

@ -30,7 +30,7 @@ function TcpIn(n) {
this.server = (typeof n.server == 'boolean')?n.server:(n.server == "server");
this.closing = false;
var node = this;
if (!node.server) {
var buffer = null;
var client;
@ -41,13 +41,13 @@ function TcpIn(n) {
buffer = (node.datatype == 'buffer')? new Buffer(0):"";
node.log("connected to "+node.host+":"+node.port);
});
client.on('data', function (data) {
if (node.datatype != 'buffer') {
data = data.toString(node.datatype);
}
if (node.stream) {
if ((typeof data) === "string" && node.newline != "") {
if ((node.datatype) === "utf8" && node.newline != "") {
buffer = buffer+data;
var parts = buffer.split(node.newline);
for (var i = 0;i<parts.length-1;i+=1) {
@ -100,7 +100,7 @@ function TcpIn(n) {
if (node.datatype != 'buffer') {
data = data.toString(node.datatype);
}
if (node.stream) {
if ((typeof data) === "string" && node.newline != "") {
buffer = buffer+data;
@ -128,21 +128,21 @@ function TcpIn(n) {
node.send(msg);
buffer = null;
}
});
});
socket.on('error',function(err) {
node.log(err);
});
});
server.listen(node.port);
node.log('listening on port '+node.port);
this._close = function() {
this.closing = true;
server.close();
node.log('stopped listening on port '+node.port);
}
}
}
RED.nodes.registerType("tcp in",TcpIn);
@ -150,4 +150,3 @@ RED.nodes.registerType("tcp in",TcpIn);
TcpIn.prototype.close = function() {
this._close();
}

176
nodes/io/32-udp.html Normal file
View File

@ -0,0 +1,176 @@
<!--
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.
-->
<!-- The Input Node -->
<script type="text/x-red" data-template-name="udp in">
<div class="form-row">
<label for="node-input-port"><i class="icon-inbox"></i> Listen</label>
on port <input type="text" id="node-input-port" placeholder="Port" style="width: 45px">
for <select id="node-input-multicast" style='width:40%'>
<option value="false">udp messages</option>
<option value="true">multicast messages</option>
</select>
</div>
<div class="form-row node-input-group">
<label for="node-input-group"><i class="icon-list"></i> Group</label>
<input type="text" id="node-input-group" placeholder="225.0.18.83">
</div>
<div class="form-row node-input-iface">
<label for="node-input-iface"><i class="icon-random"></i> Interface</label>
<input type="text" id="node-input-iface" placeholder="eth0">
</div>
<div class="form-row">
<label for="node-input-datatype"><i class="icon-file"></i> Output</label>
<select id="node-input-datatype" style="width: 70%;">
<option value="buffer">a Buffer</option>
<option value="utf8">a String</option>
<option value="base64">a Base64 encoded string</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">Tip: Make sure your firewall will allow the data in.</div>
<script>
$("#node-input-multicast").change(function() {
var id = $("#node-input-multicast option:selected").val();
if (id == "false") {
$(".node-input-group").hide();
$(".node-input-iface").hide();
}
else {
$(".node-input-group").show();
$(".node-input-iface").show();
}
});
</script>
</script>
<script type="text/x-red" data-help-name="udp in">
<p>A udp input node, that produces a <b>msg.payload</b> containing a <i>BUFFER</i>, string, or base64 encoded string. Supports multicast.</p>
<p>It also provides <b>msg.fromip</b> in the form ipaddress:port .</p>
</script>
<script type="text/javascript">
RED.nodes.registerType('udp in',{
category: 'input',
color:"Silver",
defaults: {
name: {value:""},
host: {value:""},
iface: {value:""},
port: {value:"",required:true,validate:RED.validators.number()},
datatype: {value:"buffer",required:true},
multicast: {value:"false"},
group: {value:"",validate:function(v) { return (this.multicast !== "true")||v.length > 0;} }
},
inputs:0,
outputs:1,
icon: "bridge-dash.png",
label: function() {
if (this.multicast=="false") {
return this.name||"udp "+this.port;
}
else return this.name||"udp "+(this.group+":"+this.port);
},
labelStyle: function() {
return this.name?"node_label_italic":"";
}
});
</script>
<!-- The Output Node -->
<script type="text/x-red" data-template-name="udp out">
<div class="form-row">
<label for="node-input-port"><i class="icon-envelope"></i> Send a</label>
<select id="node-input-multicast" style='width:40%'>
<option value="false">udp message</option>
<option value="broad">broadcast message</option>
<option value="multi">multicast message</option>
</select>
to port <input type="text" id="node-input-port" placeholder="Port" style="width: 45px">
</div>
<div class="form-row node-input-addr">
<label for="node-input-addr" id="node-input-addr-label"><i class="icon-list"></i> Address</label>
<input type="text" id="node-input-addr" placeholder="destination ip" style="width: 70%;">
</div>
<div class="form-row node-input-iface">
<label for="node-input-iface"><i class="icon-random"></i> Interface</label>
<input type="text" id="node-input-iface" placeholder="eth0">
</div>
<div class="form-row">
<label>&nbsp;</label>
<input type="checkbox" id="node-input-base64" placeholder="base64" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-input-base64" style="width: 70%;">Decode Base64 encoded payload ?</label>
</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>
$("#node-input-multicast").change(function() {
var id = $("#node-input-multicast option:selected").val();
console.log(id,$("#node-input-addr")[0].placeholder);
if (id !== "multi") {
$(".node-input-iface").hide();
$("#node-input-addr-label").html('<i class="icon-list"></i> Address');
$("#node-input-addr")[0].placeholder = 'destination ip';
}
else {
$(".node-input-iface").show();
$("#node-input-addr-label").html('<i class="icon-list"></i> Group');
$("#node-input-addr")[0].placeholder = '225.0.18.83';
}
if (id === "broad") {
$("#node-input-addr")[0].placeholder = '255.255.255.255';
}
});
</script>
</script>
<script type="text/x-red" data-help-name="udp out">
<p>This node sends <b>msg.payload</b> to the designated udp host and port. Supports multicast.</p>
<p>If you select broadcast either set the address to the local broadcast ip address, or maybe try 255.255.255.255, which is the global broadcast address.</p>
<p>On some systems you may need to be root to use broadcast.</p>
</script>
<script type="text/javascript">
RED.nodes.registerType('udp out',{
category: 'output',
color:"Silver",
defaults: {
name: {value:""},
addr: {value:"",required:true},
//group: {value:""},
iface: {value:""},
port: {value:"",required:true,validate:RED.validators.number()},
base64: {value:false,required:true},
multicast: {value:"false"}
},
inputs:1,
outputs:0,
icon: "bridge-dash.png",
align: "right",
label: function() {
return this.name||"udp "+(this.addr+":"+this.port);
},
labelStyle: function() {
return this.name?"node_label_italic":"";
}
});
</script>

115
nodes/io/32-udp.js Normal file
View File

@ -0,0 +1,115 @@
/**
* 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("../../red/red");
var dgram = require('dgram');
// The Input Node
function UDPin(n) {
RED.nodes.createNode(this,n);
this.group = n.group;
this.port = n.port;
this.host = n.host || null;
this.datatype = n.datatype;
this.iface = n.iface || null;
this.multicast = n.multicast;
var node = this;
var server = dgram.createSocket('udp4');
server.on("error", function (err) {
console.log("udp listener error:\n" + err.stack);
server.close();
});
server.on('message', function (message, remote) {
var msg;
if (node.datatype =="base64") { msg = { payload:message.toString('base64'), fromip:remote.address+':'+remote.port }; }
else if (node.datatype =="utf8") { msg = { payload:message.toString('utf8'), fromip:remote.address+':'+remote.port }; }
else { msg = { payload:message, fromip:remote.address+':'+remote.port }; }
node.send(msg);
});
server.on('listening', function () {
var address = server.address();
node.log('udp listener at ' + address.address + ":" + address.port);
if (node.multicast == "true") {
server.setBroadcast(true)
server.setMulticastTTL(128);
server.addMembership(node.group,node.iface);
node.log("udp multicast group "+node.group);
}
});
node.on("close", function() {
try {
server.close();
node.log('udp listener stopped');
}
catch (err) { console.log(err); }
});
server.bind(node.port,node.host);
}
RED.nodes.registerType("udp in",UDPin);
// The Output Node
function UDPout(n) {
RED.nodes.createNode(this,n);
//this.group = n.group;
this.port = n.port;
this.base64 = n.base64;
this.addr = n.addr;
this.iface = n.iface || null;
this.multicast = n.multicast;
var node = this;
var sock = dgram.createSocket('udp4'); // only use ipv4 for now
sock.bind(node.port); // have to bind before you can enable broadcast...
if (this.multicast != "false") {
sock.setBroadcast(true); // turn on broadcast
if (this.multicast == "multi") {
sock.setMulticastTTL(128);
sock.addMembership(node.addr,node.iface); // Add to the multicast group
node.log('udp multicast ready : '+node.addr+":"+node.port);
}
else node.log('udp broadcast ready : '+node.addr+":"+node.port);
}
else node.log('udp ready : '+node.addr+":"+node.port);
node.on("input", function(msg) {
if (msg.payload != null) {
//console.log("UDP:",msg.payload);
var message;
if (node.base64) { message = new Buffer(b64string, 'base64'); }
else { message = new Buffer(""+msg.payload); }
console.log("UDP send :",node.addr,node.port);
sock.send(message, 0, message.length, node.port, node.addr, function(err, bytes) {
if (err) node.error("udp : "+err);
});
}
});
node.on("close", function() {
try {
sock.close();
node.log('udp output stopped');
}
catch (err) { console.log(err); }
});
}
RED.nodes.registerType("udp out",UDPout);

View File

@ -29,8 +29,8 @@
pathname += "/";
}
var callback = encodeURIComponent(location.protocol+"//"+location.hostname+":"+location.port+pathname+"twitter/"+twitterConfigNodeId+"/auth/callback");
$("#node-config-twitter-row").html('Click <a id="node-config-twitter-start" href="/twitter/'+twitterConfigNodeId+'/auth?callback='+callback+'" target="_blank">here</a> to authenticate with Twitter.');
$("#node-config-twitter-row").html('Click <a id="node-config-twitter-start" href="/twitter/'+twitterConfigNodeId+'/auth?callback='+callback+'" target="_blank"><b>here</b></a> to authenticate with Twitter.');
$("#node-config-twitter-start").click(function() {
twitterConfigNodeIntervalId = window.setTimeout(pollTwitterCredentials,2000);
});
@ -102,34 +102,33 @@
</script>
<script type="text/x-red" data-template-name="twitter in">
<div class="form-row">
<label for="node-input-twitter"><i class="icon-user"></i> Twitter</label>
<div class="form-row">
<label for="node-input-twitter"><i class="icon-user"></i> Log in as</label>
<input type="text" id="node-input-twitter">
</div>
<div class="form-row">
<label for="node-input-user"><i class="icon-search"></i> Search</label>
<select type="text" id="node-input-user" style="display: inline-block; vertical-align: middle; width:60%;">
<option value="false">all public tweets</option>
<option value="true">the tweets of who you follow</option>
</select>
</div>
<div class="form-row">
<label for="node-input-tags"><i class="icon-tag"></i> Tags</label>
<input type="text" id="node-input-tags" placeholder="comma-separated tags">
</div>
<div class="form-row">
<label>&nbsp;</label>
<input type="checkbox" id="node-input-user" placeholder="Name" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-input-user" style="width: 70%;">Tick to use user stream<br/>(rather than status/filter)</label>
</div>
<div class="form-row">
<label for="node-input-topic"><i class="icon-tasks"></i> Topic</label>
<input type="text" id="node-input-topic" placeholder="Topic">
<label for="node-input-tags"><i class="icon-tags"></i> for</label>
<input type="text" id="node-input-tags" placeholder="comma-separated words, @ids, #tags">
</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">Tip: the Senders name gets appended to the topic heirarchy
</div>
<div class="form-tips">Tip: Use commas without spaces between multiple search terms. Comma = OR, Space = AND.
<br/>The Twitter API WILL NOT deliver 100% of all tweets.
<br/>Tweets of who you follow will include their retweets and favourites.</div>
</script>
<script type="text/x-red" data-help-name="twitter in">
<p>Twitter input node. Watches the public stream for tweets containing the configured search term.</p>
<p>Sets the <b>msg.topic</b> to the configured topic and then appends the senders screen name.</p>
<p>Twitter input node. Watches either the public or the user's stream for tweets containing the configured search term.</p>
<p>Sets the <b>msg.topic</b> to <i>tweets/</i> and then appends the senders screen name.</p>
<p>Sets <b>msg.location</b> to the tweeters location if known.</p>
</script>
@ -140,7 +139,7 @@
defaults: {
twitter: {type:"twitter-credentials",required:true},
tags: {value:"",required:true},
user: {value:false},
user: {value:"false",required:true},
name: {value:""},
topic: {value:"tweets"}
},

View File

@ -24,14 +24,13 @@ function TwitterNode(n) {
}
RED.nodes.registerType("twitter-credentials",TwitterNode);
function TwitterInNode(n) {
RED.nodes.createNode(this,n);
this.active = true;
this.user = n.user;
this.tags = n.tags.replace(/ /g,'');
this.twitter = n.twitter;
this.topic = n.topic;
this.topic = n.topic||"tweets";
this.twitterConfig = RED.nodes.getNode(this.twitter);
var credentials = RED.nodes.getCredentials(this.twitter);
@ -47,11 +46,12 @@ function TwitterInNode(n) {
if (this.tags !== "") {
try {
var thing = 'statuses/filter';
if (this.user) { thing = 'user'; }
if (this.user == "true") { thing = 'user'; }
function setupStream() {
if (node.active) {
twit.stream(thing, { track: [node.tags] }, function(stream) {
//twit.stream('user', { track: [node.tags] }, function(stream) {
//twit.stream('site', { track: [node.tags] }, function(stream) {
//twit.stream('statuses/filter', { track: [node.tags] }, function(stream) {
node.stream = stream;
stream.on('data', function(tweet) {
@ -101,8 +101,6 @@ TwitterInNode.prototype.close = function() {
}
}
function TwitterOutNode(n) {
RED.nodes.createNode(this,n);
this.topic = n.topic;

View File

@ -16,7 +16,7 @@
<script type="text/x-red" data-template-name="notify">
<div class="form-row">
<label for="node-input-title"><i class="icon-tag"></i> Title</label>
<label for="node-input-title"><i class="icon-flag"></i> Title</label>
<input type="text" id="node-input-title" placeholder="Node-RED">
</div>
<div class="form-row">

View File

@ -37,7 +37,7 @@ if (pushkey) {
function ProwlNode(n) {
RED.nodes.createNode(this,n);
this.title = n.title;
this.priority = n.priority * 1;
this.priority = parseInt(n.priority);
if (this.priority > 2) this.priority = 2;
if (this.priority < -2) this.priority = -2;
var node = this;

View File

@ -65,12 +65,12 @@
<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">Sending complete object will stringify the whole msg object before sending.</div>
<div class="form-tips">Sending the complete object will stringify the whole msg object before sending.</div>
</script>
<script type="text/x-red" data-help-name="irc out">
<p>Connects to a channel on an IRC server</p>
<p>If you send something with NO msg.topic it will go to the channel - otherwise it will go to the id in the <b>msg.topic</b> field.</p>
<p>Sends messages to a channel on an IRC server</p>
<p>If you send something with NO <b>msg.topic</b> it will go to the configured channel - otherwise it will go to the id in the <b>msg.topic</b> field.</p>
<p>You can either just send the <b>msg.payload</b>, or you can send the complete <b>msg</b> object.</p>
</script>
@ -103,7 +103,7 @@
</div>
<div class="form-row">
<label for="node-config-input-channel"><i class="icon-tasks"></i> Channel</label>
<input type="text" id="node-config-input-channel" placeholder="#testing1234">
<input type="text" id="node-config-input-channel" placeholder="#node-red">
</div>
<div class="form-row">
<label for="node-config-input-nickname"><i class="icon-tasks"></i> Nickname</label>

View File

@ -16,6 +16,7 @@
var RED = require("../../red/red");
var irc = require("irc");
var util = require("util");
// The Server Definition - this opens (and closes) the connection
function IRCServerNode(n) {
@ -23,40 +24,38 @@ function IRCServerNode(n) {
this.server = n.server;
this.channel = n.channel;
this.nickname = n.nickname;
this.ircclient = new irc.Client(this.server, this.nickname, {
channels: [this.channel]
this.ircclient = null;
this.on("close", function() {
if (this.ircclient != null) {
this.ircclient.disconnect();
}
});
this._close = function() {
this.ircclient.disconnect();
}
}
RED.nodes.registerType("irc-server",IRCServerNode);
IRCServerNode.prototype.close = function() {
this._close();
}
// The Input Node
function IrcInNode(n) {
RED.nodes.createNode(this,n);
this.ircserver = n.ircserver;
this.serverConfig = RED.nodes.getNode(this.ircserver);
if (this.serverConfig.ircclient == null) {
this.serverConfig.ircclient = new irc.Client(this.serverConfig.server, this.serverConfig.nickname, {
channels: [this.serverConfig.channel]
});
this.serverConfig.ircclient.addListener('error', function(message) {
util.log('[irc] '+ JSON.stringify(message));
});
}
this.ircclient = this.serverConfig.ircclient;
var node = this;
this.ircclient.addListener('message', function (from, to, message) {
console.log(from + ' => ' + to + ': ' + message);
var msg = { "topic":from, "to":to, "payload":message };
node.send(msg);
});
this.ircclient.addListener('error', function(message) {
node.error(JSON.stringify(message));
});
}
RED.nodes.registerType("irc in",IrcInNode);
@ -66,12 +65,20 @@ function IrcOutNode(n) {
this.sendAll = n.sendObject;
this.ircserver = n.ircserver;
this.serverConfig = RED.nodes.getNode(this.ircserver);
this.ircclient = this.serverConfig.ircclient;
this.channel = this.serverConfig.channel;
if (this.serverConfig.ircclient == null) {
this.serverConfig.ircclient = new irc.Client(this.serverConfig.server, this.serverConfig.nickname, {
channels: [this.serverConfig.channel]
});
this.serverConfig.ircclient.addListener('error', function(message) {
util.log('[irc] '+ JSON.stringify(message));
});
}
this.ircclient = this.serverConfig.ircclient;
var node = this;
this.on("input", function(msg) {
console.log(msg);
//console.log(msg,node.channel);
if (node.sendAll) {
node.ircclient.say(node.channel, JSON.stringify(msg));
}

View File

@ -14,8 +14,17 @@
* limitations under the License.
**/
var orig=console.warn;
console.warn=(function() { // suppress warning from stringprep when not needed)
var orig=console.warn;
return function() {
//orig.apply(console, arguments);
};
})();
var RED = require("../../red/red");
var xmpp = require('simple-xmpp');
console.warn = orig;
try {
var xmppkey = require("../../settings").xmpp || require("../../../xmppkeys.js");
@ -97,17 +106,13 @@ function XmppNode(n) {
}
});
this._close = function() {
this.on("close", function() {
xmpp.setPresence('offline');
//xmpp.conn.end();
// TODO - DCJ NOTE... this is not good. It leaves the connection up over a restart - which will end up with bad things happening...
// (but requires the underlying xmpp lib to be fixed (which does have an open bug request on fixing the close method)).
this.warn("Due to an underlying bug in the xmpp library this does not disconnect old sessions. This is bad... A restart would be better.");
}
});
}
RED.nodes.registerType("xmpp",XmppNode);
XmppNode.prototype.close = function() {
this._close();
}

BIN
public/icons/bluetooth.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@ -143,8 +143,34 @@ RED.editor = function() {
var changes = {};
var changed = false;
var wasDirty = RED.view.dirty();
if (editing_node._def.oneditsave) {
var oldValues = {};
for (var d in editing_node._def.defaults) {
if (typeof editing_node[d] === "string" || typeof editing_node[d] === "number") {
oldValues[d] = editing_node[d];
} else {
oldValues[d] = $.extend(true,{},{v:editing_node[d]}).v;
}
}
editing_node._def.oneditsave.call(editing_node);
for (var d in editing_node._def.defaults) {
if (oldValues[d] === null || typeof oldValues[d] === "string" || typeof oldValues[d] === "number") {
if (oldValues[d] !== editing_node[d]) {
changes[d] = oldValues[d];
changed = true;
}
} else {
if (JSON.stringify(oldValues[d]) !== JSON.stringify(editing_node[d])) {
changes[d] = oldValues[d];
changed = true;
}
}
}
}
if (editing_node._def.defaults) {
@ -177,7 +203,6 @@ RED.editor = function() {
changes[d] = editing_node[d];
editing_node[d] = newValue;
changed = true;
RED.view.dirty(true);
}
}
}
@ -185,6 +210,7 @@ RED.editor = function() {
var removedLinks = updateNodeProperties(editing_node);
if (changed) {
RED.view.dirty(true);
RED.history.push({t:'edit',node:editing_node,changes:changes,links:removedLinks,dirty:wasDirty});
}

View File

@ -63,7 +63,10 @@ RED.palette = function() {
container:'body',
content: $(($("script[data-help-name|='"+nt+"']").html()||"<p>no information available</p>").trim())[0]
});
$(d).click(function() {
var help = '<div class="node-help">'+($("script[data-help-name|='"+d.type+"']").html()||"")+"</div>";
$("#tab-info").html(help);
});
$(d).draggable({
helper: 'clone',
appendTo: 'body',

View File

@ -649,7 +649,13 @@ RED.view = function() {
//mainRect.on("touchend",nodeMouseUp);
if (d._def.icon) {
var icon = node.append("image").attr("xlink:href","icons/"+d._def.icon).attr("class","node_icon").attr("x",0).attr("y",0).attr("width","15").attr("height",function(d){return Math.min(50,d.h);});
var icon = node.append("image")
.attr("xlink:href","icons/"+d._def.icon)
.attr("class","node_icon")
.attr("x",0).attr("y",function(d){return (d.h-Math.min(50,d.h))/2;})
.attr("width","15")
.attr("height", function(d){return Math.min(50,d.h);});
if (d._def.align) {
icon.attr('class','node_icon node_icon_'+d._def.align);
}
@ -747,7 +753,7 @@ RED.view = function() {
var port = d3.select(this);
port.attr("y",function(d){return (d.h/2)-5;})
});
thisNode.selectAll(".node_icon").attr("height",function(d){return Math.min(50,d.h);});
thisNode.selectAll(".node_icon").attr("height",function(d){return Math.min(50,d.h);}).attr("y",function(d){return (d.h-Math.min(50,d.h))/2;});
thisNode.selectAll('.node_right_button_group').attr("transform",function(d){return "translate("+(d.w-100)+","+0+")";});
thisNode.selectAll('.node_right_button').attr("transform",function(d){return "translate("+(d.w-100)+","+0+")";}).attr("fill",function(d) {

26
red.js
View File

@ -21,7 +21,6 @@ var crypto = require("crypto");
var settings = require("./settings");
var RED = require("./red/red.js");
var server;
var app = express();
@ -49,11 +48,30 @@ if (settings.httpAuth) {
);
}
settings.flowFile = process.argv[2] || settings.flowFile;
var red = RED.init(server,settings);
app.use(settings.httpRoot,red);
server.listen(settings.uiPort);
RED.start();
util.log('[red] Server now running at http'+(settings.https?'s':'')+'://127.0.0.1:'+settings.uiPort+settings.httpRoot);
server.listen(settings.uiPort,function() {
util.log('[red] Server now running at http'+(settings.https?'s':'')+'://127.0.0.1:'+settings.uiPort+settings.httpRoot);
});
process.on('uncaughtException',function(err) {
if (err.errno === "EADDRINUSE") {
util.log('[red] Unable to listen on http'+(settings.https?'s':'')+'://127.0.0.1:'+settings.uiPort+settings.httpRoot);
util.log('[red] Error: port in use');
} else {
util.log('[red] Uncaught Exception:');
util.log(err.stack);
}
process.exit(1);
});
process.on('SIGINT', function () {
RED.stop();
util.log('[red] Exiting Node-RED. Thank you.');
process.exit();
});

View File

@ -42,7 +42,6 @@ function getCallerFilename(type) {
return stack[0].getFileName();
}
var registry = (function() {
var nodes = {};
var logHandlers = [];
@ -91,7 +90,6 @@ var node_type_registry = (function() {
var obj = {
register: function(type,node) {
util.inherits(node, Node);
var callerFilename = getCallerFilename(type);
if (callerFilename == null) {
util.log("["+type+"] unable to determine filename");
@ -117,7 +115,6 @@ var node_type_registry = (function() {
result += node_configs[nt];
}
return result;
}
}
return obj;
@ -176,7 +173,6 @@ Node.prototype.send = function(msg) {
}
module.exports.Node = Node;
Node.prototype.receive = function(msg) {
this.emit("input",msg);
}
@ -197,9 +193,6 @@ Node.prototype.error = function(msg) {
this.emit("log",o);
}
var credentials = {};
var credentialsFile = "credentials.json";
if (fs.existsSync(credentialsFile)) {
@ -225,8 +218,6 @@ module.exports.deleteCredentials = function(id) {
delete credentials[id];
saveCredentialsFile();
}
module.exports.createNode = function(node,def) {
Node.call(node,def);
}
@ -257,12 +248,9 @@ module.exports.load = function() {
});
}
loadNodes(__dirname+"/../nodes");
//events.emit("nodes-loaded");
}
var activeConfig = null;
var missingTypes = [];
@ -279,10 +267,15 @@ events.on('type-registered',function(type) {
}
});
module.exports.getNode = function(nid) {
return registry.get(nid);
}
module.exports.closedown = function() {
util.log("[red] Closing Down Nodes");
registry.clear();
}
module.exports.setConfig = function(conf) {
if (activeConfig&&activeConfig.length > 0) {
util.log("[red] Stopping flows");
@ -293,7 +286,6 @@ module.exports.setConfig = function(conf) {
}
var parseConfig = function() {
missingTypes = [];
for (var i in activeConfig) {
var type = activeConfig[i].type;
@ -307,7 +299,6 @@ var parseConfig = function() {
for (var i in missingTypes) {
util.log("[red] - "+missingTypes[i]);
}
return;
}
@ -317,19 +308,18 @@ var parseConfig = function() {
var nn = null;
var nt = node_type_registry.get(activeConfig[i].type);
if (nt) {
try {
nn = new nt(activeConfig[i]);
}
catch (err) {
util.log("[red] "+activeConfig[i].type+" : "+err);
}
try {
nn = new nt(activeConfig[i]);
}
catch (err) {
util.log("[red] "+activeConfig[i].type+" : "+err);
}
}
// console.log(nn);
if (nn == null) {
util.log("[red] unknown type: "+activeConfig[i].type);
}
}
// Clean up any orphaned credentials
var deletedCredentials = false;
for (var c in credentials) {
@ -343,5 +333,4 @@ var parseConfig = function() {
saveCredentialsFile();
}
events.emit("nodes-started");
}

View File

@ -14,7 +14,7 @@
* limitations under the License.
**/
var events = require("./events");
var events = require("./events");
var server = require("./server");
var nodes = require("./nodes");
var library = require("./library");
@ -24,23 +24,23 @@ var settings = null;
var events = require("events");
var RED = {
init: function(httpServer,userSettings) {
settings = userSettings;
server.init(httpServer,settings);
library.init();
return server.app;
},
start: server.start,
nodes: nodes,
library: library,
events: events
events: events,
stop: nodes.closedown,
};
RED.__defineGetter__("app", function() { return server.app });
RED.__defineGetter__("server", function() { return server.server });
RED.__defineGetter__("settings", function() { return settings });
module.exports = RED;
module.exports = RED;

View File

@ -18,9 +18,9 @@ var fs = require('fs');
var util = require('util');
var createUI = require("./ui");
var redNodes = require("./nodes");
var host = require('os').hostname();
//TODO: relocated user dir
var rulesfile = process.argv[2] || 'flows_'+host+'.json';
var flowfile = '';
var app = null;
var server = null;
@ -29,6 +29,8 @@ function createServer(_server,settings) {
server = _server;
app = createUI(settings);
flowfile = settings.flowFile || 'flows_'+require('os').hostname()+'.json';
//TODO: relocated user dir
fs.exists("lib/",function(exists) {
if (!exists) {
@ -43,9 +45,9 @@ function createServer(_server,settings) {
});
app.get("/flows",function(req,res) {
fs.exists(rulesfile, function (exists) {
fs.exists(flowfile, function (exists) {
if (exists) {
res.sendfile(rulesfile);
res.sendfile(flowfile);
} else {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.write("[]");
@ -62,7 +64,7 @@ function createServer(_server,settings) {
req.on('end', function() {
res.writeHead(204, {'Content-Type': 'text/plain'});
res.end();
fs.writeFile(rulesfile, fullBody, function(err) {
fs.writeFile(flowfile, fullBody, function(err) {
if(err) {
util.log(err);
} else {
@ -87,14 +89,14 @@ function start() {
util.log("------------------------------------------");
fs.exists(rulesfile, function (exists) {
fs.exists(flowfile, function (exists) {
if (exists) {
util.log("[red] Loading flows : "+rulesfile);
fs.readFile(rulesfile,'utf8',function(err,data) {
util.log("[red] Loading flows : "+flowfile);
fs.readFile(flowfile,'utf8',function(err,data) {
redNodes.setConfig(JSON.parse(data));
});
} else {
util.log("[red] Flows file not found : "+rulesfile);
util.log("[red] Flows file not found : "+flowfile);
}
});
}

View File

@ -19,6 +19,9 @@ module.exports = {
serialReconnectTime: 15000,
debugMaxLength: 1000,
// The file containing the flows. If not set, it defaults to flows_<hostname>.json
//flowFile: 'flows.json',
// You can protect the user interface with a userid and password by using the following property
// the password must be an md5 hash eg.. 5f4dcc3b5aa765d61d8327deb882cf99 ('password')
//httpAuth: {user:"user",pass:"5f4dcc3b5aa765d61d8327deb882cf99"},