Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Ben Hardill 2013-09-19 16:22:39 +01:00
commit b110e4de17
28 changed files with 1002 additions and 184 deletions

View File

@ -16,18 +16,20 @@
<script type="text/x-red" data-template-name="debug">
<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">
<label for="node-input-complete"><i class="icon-list"></i> Output</label>
<select type="text" id="node-input-complete" style="display: inline-block; width: auto; vertical-align: top;">
<option value=false>Payload only</option>
<option value=true>Complete msg object</option>
</select>
</div>
<div class="form-row">
<label>&nbsp;</label>
<input type="checkbox" id="node-input-complete" placeholder="Complete" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-input-complete" style="width: 70%;">Show complete msg object ?</label>
<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="debug">
<p>The Debug node can be connected to the output of any node. It will display the timestamp, <b>msg.topic</b> and <b>msg.payload</b> fields of any messages it receives in the debug tab of the sidebar.
<p>The Debug node can be connected to the output of any node. It will display the timestamp, <b>msg.topic</b> and <b>msg.payload</b> fields of any messages it receives in the debug tab of the sidebar.
<br/>The sidebar can be accessed under the options drop-down in the top right corner.</p>
<p>The button to the right of the node will toggle it's output on and off so you can de-clutter the debug window.</p>
<p>If the payload is an object it will be stringified first for display and indicate that by saying "(Object) ".</p>
@ -113,10 +115,16 @@
var sbc = document.getElementById("debug-content");
var errornotification = null;
function debugConnect() {
//console.log("debug ws connecting");
var ws = new WebSocket("ws://"+location.hostname+":"+location.port+document.location.pathname+"/debug");
ws.onopen = function() {
if (errornotification) {
errornotification.close();
errornotification = null;
}
//console.log("debug ws connected");
}
ws.onmessage = function(event) {
@ -158,7 +166,9 @@
}
};
ws.onclose = function() {
//console.log("debug ws closed");
if (errornotification == null) {
errornotification = RED.notify("<b>Error</b>: Lost connection to server","error",true);
}
setTimeout(debugConnect,1000);
}
}

View File

@ -19,21 +19,25 @@ var RED = require("../../red/red");
var util = require("util");
var ws = require('ws');
var events = require("events");
var debuglength = RED.settings.debugMaxLength||1000;
function DebugNode(n) {
RED.nodes.createNode(this,n);
this.name = n.name;
this.complete = n.complete;
this.active = (n.active == null)||n.active;
this.on("input",function(msg) {
if (this.active) {
if (msg.payload instanceof Buffer) {
msg.payload = "(Buffer) "+msg.payload.toString();
}
if (this.complete) {
if (this.complete == "true") {
DebugNode.send({id:this.id,name:this.name,topic:msg.topic,msg:msg,_path:msg._path});
} else {
DebugNode.send({id:this.id,name:this.name,topic:msg.topic,msg:msg.payload,_path:msg._path});
if (typeof msg.payload !== "undefined") {
DebugNode.send({id:this.id,name:this.name,topic:msg.topic,msg:msg.payload,_path:msg._path});
}
}
}
});
@ -46,18 +50,23 @@ DebugNode.send = function(msg) {
msg.msg = msg.msg.toString();
}
else if (typeof msg.msg === 'object') {
try {
msg.msg = "(Object) "+JSON.stringify(msg.msg,null,1);
}
catch (err) {
console.log(msg.msg);
console.log(err);
msg.msg = "[Error] Can't stringify object with circular reference - see console log.";
}
var seen = [];
msg.msg = "(Object) " + JSON.stringify(msg.msg, function(key, value) {
if (typeof value === 'object' && value !== null) {
if (seen.indexOf(value) !== -1) { return "[circular]"; }
seen.push(value);
}
return value;
}," ");
seen = null;
}
else if (typeof msg.msg === "boolean") msg.msg = "(boolean) "+msg.msg.toString();
else if (msg.msg === 0) msg.msg = "0";
if (msg.msg.length > debuglength) {
msg.msg = msg.msg.substr(0,debuglength) +" ....";
}
for (var i in DebugNode.activeConnections) {
var ws = DebugNode.activeConnections[i];
try {

View File

@ -19,10 +19,16 @@
<label for="node-input-name"><i class="icon-tag"></i> Comment</label>
<input type="text" id="node-input-name" placeholder="Comment">
</div>
<div class="form-row">
<label for="node-input-info"><i class="icon-file"></i> More</label>
<input type="hidden" id="node-input-info">
<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>
</script>
<script type="text/x-red" data-help-name="comment">
<p>Simple comment block. More of a label really...</p>
<p>Simple comment block.</p>
</script>
<script type="text/javascript">
@ -30,7 +36,8 @@
category: 'function',
color:"#ffffff",
defaults: {
name: {value:""}
name: {value:""},
info: {value:""}
},
inputs:0,
outputs:0,
@ -40,6 +47,39 @@
},
labelStyle: function() {
return this.name?"node_label_italic":"";
},
oneditprepare: function() {
$( "#node-input-outputs" ).spinner({
min:1
});
function functionDialogResize(ev,ui) {
$("#node-input-info-editor").css("height",(ui.size.height-235)+"px");
};
$( "#dialog" ).on("dialogresize", functionDialogResize);
$( "#dialog" ).one("dialogopen", function(ev) {
var size = $( "#dialog" ).dialog('option','sizeCache-function');
if (size) {
functionDialogResize(null,{size:size});
}
});
$( "#dialog" ).one("dialogclose", function(ev,ui) {
var height = $( "#dialog" ).dialog('option','height');
$( "#dialog" ).off("dialogresize",functionDialogResize);
});
var that = this;
require(["orion/editor/edit"], function(edit) {
that.editor = edit({
parent:document.getElementById('node-input-info-editor'),
lang:"text",
showLinesRuler:false,
showFoldingRuler:false,
contents: $("#node-input-info").val()
});
});
},
oneditsave: function() {
$("#node-input-info").val(this.editor.getText());
delete this.editor;
}
});
</script>

View File

@ -0,0 +1,126 @@
<!--
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.
-->
<script type="text/x-red" data-template-name="rpi-gpio in">
<div class="form-row">
<label for="node-input-pin"><i class="icon-asterisk"></i> GPIO Pin</label>
<select type="text" id="node-input-pin" style="width: 150px;">
<option value="-">select pin</option>
<option value="7">7</option>
<option value="11">11</option>
<option value="12">12</option>
<option value="13">13</option>
<option value="15">15</option>
<option value="16">16</option>
<option value="18">18</option>
<option value="22">22</option>
</select>
</div>
<div class="form-row">
<label for="node-input-intype"><i class=" icon-resize-full"></i> Resistor?</label>
<select type="text" id="node-input-intype" style="width: 150px;">
<option value="tri">none</option>
<option value="up">pullup</option>
<option value="down">pulldown</option>
<!--<option value="tri">tristate</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: Only Digital I/O is supported - input must be 0 or 1.</div>
</script>
<script type="text/x-red" data-help-name="rpi-gpio in">
<p>Raspberry Pi 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>You may also enable the input pullup resitor or the pulldown resistor.</p>
<p>The <b>msg.topic</b> is set to <i>pi/{the pin number}</i></p>
<p><b>Note:</b> we are using the actual physical pin numbers on connector P1 as they are easier to locate.</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>
</script>
<script type="text/javascript">
RED.nodes.registerType('rpi-gpio in',{
category: 'advanced-input',
color:"#c6dbef",
defaults: {
name: { value:"" },
intype: { value: "in" },
pin: { value:"",required:true,validate:RED.validators.number() },
},
inputs:0,
outputs:1,
icon: "rpi.png",
label: function() {
return this.name||"Pin: "+this.pin ;
},
labelStyle: function() {
return this.name?"node_label_italic":"";
}
});
</script>
<script type="text/x-red" data-template-name="rpi-gpio out">
<div class="form-row">
<label for="node-input-pin"><i class="icon-asterisk"></i> GPIO Pin</label>
<select type="text" id="node-input-pin" style="width: 150px;">
<option value="-">select pin</option>
<option value="7">7</option>
<option value="11">11</option>
<option value="12">12</option>
<option value="13">13</option>
<option value="15">15</option>
<option value="16">16</option>
<option value="18">18</option>
<option value="22">22</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: Only Digital I/O is supported - input must be 0 or 1.</div>
</script>
<script type="text/x-red" data-help-name="rpi-gpio out">
<p>Raspberry Pi output node. Expects a <b>msg.payload</b> with either a 0 or 1 (or true or false). Requires the gpio command to work.</p>
<p>Will set the selected physical pin high or low depending on the value passed in.</p>
<p><b>Note:</b> we are using the actual physical pin numbers on connector P1 as they are easier to locate.</p>
</script>
<script type="text/javascript">
RED.nodes.registerType('rpi-gpio out',{
category: 'advanced-output',
color:"#c6dbef",
defaults: {
name: { value:"" },
pin: { value:"",required:true,validate:RED.validators.number() },
},
inputs:1,
outputs:0,
icon: "rpi.png",
align: "right",
label: function() {
return this.name||"Pin: "+this.pin;
},
labelStyle: function() {
return this.name?"node_label_italic":"";
}
});
</script>

View File

@ -0,0 +1,141 @@
/**
* 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 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');
}
// 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
"7":"7",
"11":"0",
"12":"1",
"13":"2",
"15":"3",
"16":"4",
"18":"5",
"22":"6"
}
var tablepin = {
// WiringPi : Physical
"7":"7",
"0":"11",
"1":"12",
"2":"13",
"3":"15",
"4":"16",
"5":"18",
"6":"22"
}
function GPIOInNode(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:"pi/"+tablepin[node.pin], payload:node.buttonState};
node.send(msg);
}
}
}
});
}, 250);
}
});
}
else {
this.error("Invalid GPIO pin: "+this.pin);
}
}
function GPIOOutNode(n) {
RED.nodes.createNode(this,n);
this.pin = pintable[n.pin];
var node = this;
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 reset",function(err,stdout,stderr) {
if (err) {
util.log('[36-rpi-gpio.js] Error: "gpio reset" command failed for some reason.');
}
exec("gpio load spi",function(err,stdout,stderr) {
if (err) {
util.log('[36-rpi-gpio.js] Error: "gpio load spi" command failed for some reason.');
}
RED.nodes.registerType("rpi-gpio in",GPIOInNode);
RED.nodes.registerType("rpi-gpio out",GPIOOutNode);
GPIOInNode.prototype.close = function() {
clearInterval(this._interval);
}
GPIOOutNode.prototype.close = function() {
exec("gpio mode "+this.pin+" in");
}
});
});

View File

@ -25,7 +25,7 @@ Object.size = function(obj) {
function BlinkStick(n) {
RED.nodes.createNode(this,n);
var p1 = /^\#[A-Za-z0-9]{6}$/
var p1 = /^\#[A-Fa-f0-9]{6}$/
var p2 = /[0-9]+,[0-9]+,[0-9]+/
this.led = blinkstick.findFirst(); // maybe try findAll() (one day)
var node = this;
@ -33,21 +33,23 @@ function BlinkStick(n) {
this.on("input", function(msg) {
if (msg != null) {
if (Object.size(node.led) !== 0) {
if (p2.test(msg.payload)) {
var rgb = msg.payload.split(",");
node.led.setColor(parseInt(rgb[0])&255, parseInt(rgb[1])&255, parseInt(rgb[2])&255);
}
else {
try {
try {
if (p2.test(msg.payload)) {
var rgb = msg.payload.split(",");
node.led.setColor(parseInt(rgb[0])&255, parseInt(rgb[1])&255, parseInt(rgb[2])&255);
}
else {
node.led.setColor(msg.payload);
}
catch (err) {
node.warn("Incorrect format: "+msg.payload);
}
}
catch (err) {
node.warn("BlinkStick missing ?");
node.led = blinkstick.findFirst();
}
}
else {
node.warn("No BlinkStick found");
node.led = blinkstick.findFirst();
}
}
});

View File

@ -23,7 +23,7 @@ function Blink1Node(n) {
var node = this;
try {
var p1 = /^\#[A-Za-z0-9]{6}$/
var p1 = /^\#[A-Fa-f0-9]{6}$/
var p2 = /[0-9]+,[0-9]+,[0-9]+/
this.on("input", function(msg) {
if (blink1) {
@ -53,7 +53,7 @@ function Blink1Node(n) {
var blink1 = new Blink1.Blink1();
}
catch(e) {
node.error("no Blink1 found");
node.error("No Blink1 found");
}
}

View File

@ -0,0 +1,50 @@
<!--
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.
-->
<script type="text/x-red" data-template-name="ledborg">
<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">Expects a msg.payload with PiBorg three digit rgb colour string. 000 -> 222</div>
</script>
<script type="text/x-red" data-help-name="ledborg">
<p>PiBorg LedBorg LED output node. Expects a <b>msg.payload</b> with a three digit rgb triple, from <b>000</b> to <b>222</b>.</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><pre>0x00 - 0x57 = off<br/>0x58 - 0xA7 = 50%<br/>0xA8 - 0xFF = fully on</pre></p>
</script>
<script type="text/javascript">
RED.nodes.registerType('ledborg',{
category: 'output',
color:"GoldenRod",
defaults: {
name: {value:""}
},
inputs:1,
outputs:0,
icon: "light.png",
align: "right",
label: function() {
return this.name||"ledborg";
},
labelStyle: function() {
return this.name?"node_label_italic":"";
}
});
</script>

View File

@ -0,0 +1,53 @@
/**
* 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 util = require('util');
var fs = require('fs');
// check if /dev/ledborg exists - if not then don't even show the node.
if (!fs.existsSync("/dev/ledborg")) {
util.log("[78-ledborg.js] Error: PiBorg hardware : LedBorg not found");
return;
}
function LedBorgNode(n) {
RED.nodes.createNode(this,n);
var p1 = /[0-2][0-2][0-2]/
var p2 = /^\#[A-Fa-f0-9]{6}$/
var node = this;
this.on("input", function(msg) {
if (p1.test(msg.payload)) {
fs.writeFile('/dev/ledborg', msg.payload, function (err) {
if (err) node.warn(msg.payload+" : No LedBorg found");
});
}
if (p2.test(msg.payload)) {
var r = Math.floor(parseInt(msg.payload.slice(1,3),16)/88).toString();
var g = Math.floor(parseInt(msg.payload.slice(3,5),16)/88).toString();
var b = Math.floor(parseInt(msg.payload.slice(5),16)/88).toString();
fs.writeFile('/dev/ledborg', r+g+b, function (err) {
if (err) node.warn(r+g+b+" : No LedBorg found");
});
}
else {
node.warn("Invalid LedBorg colour code");
}
});
}
RED.nodes.registerType("ledborg",LedBorgNode);

View File

@ -37,15 +37,17 @@ function HTTPIn(n) {
}
}
RED.nodes.registerType("http in",HTTPIn);
HTTPIn.prototype.close = function() {
var routes = redUI.app.routes[this.method];
console.log(RED.app.routes[this.method]);
var routes = RED.app.routes[this.method];
for (var i in routes) {
if (routes[i].path == this.url) {
routes.splice(i,1);
break;
}
}
console.log(RED.app.routes[this.method]);
}
RED.nodes.registerType("http in",HTTPIn);

View File

@ -16,22 +16,36 @@
<script type="text/x-red" data-template-name="tcp in">
<div class="form-row">
<label for="node-input-host"><i class="icon-bookmark"></i> Host</label>
<input type="text" id="node-input-host" placeholder="localhost" style="width: 40%;" >
<label for="node-input-server"><i class="icon-resize-small"></i> Type</label>
<select id="node-input-server" style="width:120px; margin-right:5px;">
<option value="server">Listen on</option>
<option value="client">Connect to</option>
</select>
port <input type="text" id="node-input-port" style="width: 50px">
</div>
<div class="form-row hidden" id="node-input-host-row" style="padding-left: 110px;">
at host <input type="text" id="node-input-host" placeholder="localhost" style="width: 40%;">
</div>
<label for="node-input-port" style="margin-left: 10px; width: 35px;"> Port</label>
<input type="text" id="node-input-port" placeholder="Port" style="width: 45px">
</div>
<div class="form-row">
<label>&nbsp;</label>
<input type="checkbox" id="node-input-server" placeholder="server" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-input-server" style="width: 70%;">Be a server socket ?</label>
<label><i class="icon-th"></i> Output</label>
a
<select id="node-input-datamode" style="width:110px;">
<option value="stream">stream of</option>
<option value="single">single</option>
</select>
<select id="node-input-datatype" style="width:140px;">
<option value="buffer">Buffer</option>
<option value="utf8">String</option>
<option value="base64">Base64 String</option>
</select>
payload<span id="node-input-datamode-plural">s</span>
</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%;">Base64 encode payload ?</label>
<div id="node-row-newline" class="form-row hidden" style="padding-left: 110px;">
delimited by <input type="text" id="node-input-newline" style="width: 110px;">
</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">
@ -40,15 +54,11 @@
<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: sends the received data as a Buffer object (not a String).<br/>If you select server socket the host defaults to localhost.</div>
</script>
<script type="text/x-red" data-help-name="tcp in">
<p>Provides a choice of tcp input connections. Can either be a client - or provide a listening socket.</p>
<p>The TCP node produces a <i>BUFFER</i> object <b></b>msg.payload</b> and NOT a String. If you need a String then use <i>.toString()</i> on <b>msg.payload</b> in your next function block.</p>
<p>It also provides <b>msg.fromip</b> of the form ipaddress:port .</p>
<p>You can select Base64 encoding if you want to make it easy to preserve the complete message as a string.</p>
<p>In case of disconnection the client trys to reconnect every 10 secs. Topic is optional.</p>
<p>Provides a choice of tcp inputs. Can either connect to a remote tcp port,
or accept incoming connections.</p>
</script>
<script type="text/javascript">
@ -56,24 +66,51 @@
category: 'input',
color:"Silver",
defaults: {
host: {value:"127.0.0.1",required:true},
server: {value:"server",required:true},
host: {value:"",validate:function(v) { return (this.server == "server")||v.length > 0;} },
port: {value:"",required:true,validate:RED.validators.number()},
base64: {value:false,required:true},
server: {value:false,required:true},
datamode:{value:"stream"},
datatype:{value:"buffer"},
newline:{value:""},
topic: {value:""},
name: {value:""}
name: {value:""},
base64: {/*deprecated*/ value:false,required:true}
},
inputs:0,
outputs:1,
icon: "bridge-dash.png",
label: function() {
if ((this.host!="") & (this.port!="")) {
return this.name||(this.host+":"+this.port);
}
else { return "tcp in"; }
return this.name || "tcp:"+(this.host?this.host+":":"")+this.port;
},
labelStyle: function() {
return this.name?"node_label_italic":"";
},
oneditprepare: function() {
var updateOptions = function() {
var sockettype = $("#node-input-server option:selected").val();
if (sockettype == "client") {
$("#node-input-host-row").show();
} else {
$("#node-input-host-row").hide();
}
var datamode = $("#node-input-datamode option:selected").val();
var datatype = $("#node-input-datatype option:selected").val();
if (datamode == "stream") {
$("#node-input-datamode-plural").show();
if (datatype == "utf8") {
$("#node-row-newline").show();
} else {
$("#node-row-newline").hide();
}
} else {
$("#node-input-datamode-plural").hide();
$("#node-row-newline").hide();
}
};
updateOptions();
$("#node-input-server").change(updateOptions);
$("#node-input-datatype").change(updateOptions);
$("#node-input-datamode").change(updateOptions);
}
});
</script>

View File

@ -15,7 +15,7 @@
**/
var RED = require("../../red/red");
var reConnect = RED.settings.socketReconnectTime||10000;
var reconnectTime = RED.settings.socketReconnectTime||10000;
var net = require('net');
function TcpIn(n) {
@ -23,76 +23,126 @@ function TcpIn(n) {
this.host = n.host;
this.port = n.port * 1;
this.topic = n.topic;
this.stream = (!n.datamode||n.datamode=='stream'); /* stream,single*/
this.datatype = n.datatype||'buffer'; /* buffer,utf8,base64 */
this.newline = (n.newline||"").replace("\\n","\n").replace("\\r","\r");
this.base64 = n.base64;
this.server = n.server;
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;
var to;
var reconnectTimeout;
function setupTcpClient() {
node.log('connecting to port '+node.port);
node.log("connecting to "+node.host+":"+node.port);
client = net.connect(node.port, node.host, function() {
node.log("input connected to "+node.host+":"+node.port);
buffer = (node.datatype == 'buffer')? new Buffer(0):"";
node.log("connected to "+node.host+":"+node.port);
});
client.on('data', function (data) {
var msg;
if (node.base64) { msg = { topic:node.topic, payload:new Buffer(data).toString('base64') }; }
else { msg = {topic:node.topic, payload:data}; }
node.send(msg);
if (node.datatype != 'buffer') {
data = data.toString(node.datatype);
}
if (node.stream) {
if ((typeof data) === "string" && node.newline != "") {
buffer = buffer+data;
var parts = buffer.split(node.newline);
for (var i = 0;i<parts.length-1;i+=1) {
var msg = {topic:node.topic, payload:parts[i]};
node.send(msg);
}
buffer = parts[parts.length-1];
} else {
var msg = {topic:node.topic, payload:data};
node.send(msg);
}
} else {
if ((typeof data) === "string") {
buffer = buffer+data;
} else {
buffer = Buffer.concat([buffer,data],buffer.length+data.length);
}
}
});
client.on('end', function() {
node.log("ended");
if (!node.stream || (node.datatype == "utf8" && node.newline != "" && buffer.length > 0)) {
var msg = {topic:node.topic,payload:buffer};
node.send(msg);
buffer = null;
}
});
client.on('close', function() {
client.destroy();
node.log('closed');
to = setTimeout(setupTcpClient, reConnect);
node.log("connection lost to "+node.host+":"+node.port);
if (!node.closing) {
reconnectTimeout = setTimeout(setupTcpClient, reconnectTime);
}
});
client.on('error', function(err) {
node.log('error : '+err);
//to = setTimeout(setupTcpClient, reConnect);
node.log(err);
});
}
setupTcpClient();
this._close = function() {
this.closing = true;
client.end();
clearTimeout(to);
node.log('input stopped');
clearTimeout(reconnectTimeout);
}
}
else {
} else {
var server = net.createServer(function (socket) {
var buffer = null;
socket.on('data', function (chunk) {
//if (buffer == null) {
// buffer = chunk;
//} else {
//buffer = Buffer.concat([buffer,chunk]);
var msg = {topic:node.topic, payload:chunk, fromip:socket.remoteAddress+':'+socket.remotePort};
node.send(msg);
//}
});
socket.on('end', function() {
var msg = {topic:node.topic, payload:buffer, fromip:socket.remoteAddress+':'+socket.remotePort};
node.send(msg);
});
var buffer = (node.datatype == 'buffer')? new Buffer(0):"";
socket.on('data', function (data) {
if (node.datatype != 'buffer') {
data = data.toString(node.datatype);
}
if (node.stream) {
if ((typeof data) === "string" && node.newline != "") {
buffer = buffer+data;
var parts = buffer.split(node.newline);
for (var i = 0;i<parts.length-1;i+=1) {
var msg = {topic:node.topic, payload:parts[i]};
node.send(msg);
}
buffer = parts[parts.length-1];
} else {
var msg = {topic:node.topic, payload:data};
node.send(msg);
}
} else {
if ((typeof data) === "string") {
buffer = buffer+data;
} else {
buffer = Buffer.concat([buffer,data],buffer.length+data.length);
}
}
});
socket.on('end', function() {
if (!node.stream || (node.datatype == "utf8" && node.newline != "" && buffer.length > 0)) {
var msg = {topic:node.topic,payload:buffer};
node.send(msg);
buffer = null;
}
});
socket.on('error',function(err) {
node.log(err);
});
});
server.listen(node.port);
node.log('socket input on port '+node.port);
node.log('listening on port '+node.port);
this._close = function() {
this.closing = true;
server.close();
node.log('socket input stopped');
node.log('stopped listening on port '+node.port);
}
}
}
RED.nodes.registerType("tcp in",TcpIn);

View File

@ -16,34 +16,37 @@
<script type="text/x-red" data-template-name="tcp out">
<div class="form-row">
<label for="node-input-host"><i class="icon-bookmark"></i> Host</label>
<input type="text" id="node-input-host" placeholder="localhost" style="width: 40%;" >
<label for="node-input-beserver"><i class="icon-resize-small"></i> Type</label>
<select id="node-input-beserver" style="width:120px; margin-right:5px;">
<option value="server">Listen on</option>
<option value="client">Connect to</option>
</select>
port <input type="text" id="node-input-port" style="width: 50px">
</div>
<div class="form-row hidden" id="node-input-host-row" style="padding-left: 110px;">
at host <input type="text" id="node-input-host" placeholder="localhost" style="width: 40%;">
</div>
<label for="node-input-port" style="margin-left: 10px; width: 35px;"> Port</label>
<input type="text" id="node-input-port" placeholder="Port" style="width: 45px">
</div>
<div class="form-row">
<label>&nbsp;</label>
<input type="checkbox" id="node-input-beserver" placeholder="server" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-input-beserver" style="width: 70%;">Be a server socket ?</label>
</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 message ?</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>
<div class="form-tips">Tip: If you select server socket the host defaults to localhost.</div>
</script>
<script type="text/x-red" data-help-name="tcp out">
<p>Provides a choice of tcp output connections. Can either connect out - or provide a socket connection.</p>
<p>Provides a choice of tcp outputs. Can either connect to a remote tcp port,
or accept incoming connections.</p>
<p>Only <b>msg.payload</b> is sent.</p>
<p>You can select Base64 decoding if you want to decode a message encoded by the input socket.</p>
<p>If <b>msg.payload</b> is a string containing a base64 encoding of binary
data, the Base64 decoding option will cause it to be converted back to binary
before being sent.</p>
</script>
<script type="text/javascript">
@ -51,9 +54,9 @@
category: 'output',
color:"Silver",
defaults: {
host: {value:"127.0.0.1",required:true},
host: {value:"",validate:function(v) { return (this.beserver == "server")||v.length > 0;} },
port: {value:"",required:true},
beserver: {value:false,required:true},
beserver: {value:"client",required:true},
base64: {value:false,required:true},
name: {value:""}
},
@ -62,12 +65,22 @@
icon: "bridge-dash.png",
align: "right",
label: function() {
var lab = this.host+":"+this.port;
if (this.server) lab = "tcp out:"+this.port;
return this.name||lab;
return this.name || "tcp:"+(this.host?this.host+":":"")+this.port;
},
labelStyle: function() {
return (this.name)?"node_label_italic":"";
},
oneditprepare: function() {
var updateOptions = function() {
var sockettype = $("#node-input-beserver option:selected").val();
if (sockettype == "client") {
$("#node-input-host-row").show();
} else {
$("#node-input-host-row").hide();
}
};
updateOptions();
$("#node-input-beserver").change(updateOptions);
}
});
</script>

View File

@ -15,7 +15,7 @@
**/
var RED = require("../../red/red");
var reConnect = RED.settings.socketReconnectTime||10000;
var reconnectTime = RED.settings.socketReconnectTime||10000;
var net = require('net');
function TcpOut(n) {
@ -25,67 +25,93 @@ function TcpOut(n) {
this.base64 = n.base64;
this.beserver = n.beserver;
this.name = n.name;
this.closing = false;
var node = this;
if (!node.beserver) {
var client = new net.Socket();
var to;
if (!node.beserver||node.beserver=="client") {
var reconnectTimeout;
var client = null;
var connected = false;
function setupTcpClient() {
client.connect(node.port, node.host, function() {
node.log("output connected to "+node.host+":"+node.port);
node.log("connecting to "+node.host+":"+node.port);
client = net.connect(node.port, node.host, function() {
connected = true;
node.log("connected to "+node.host+":"+node.port);
});
client.on('error', function (err) {
node.error('error : '+err);
to = setTimeout(setupTcpClient, reConnect);
node.log('error : '+err);
});
client.on('end', function (err) {
node.log("output disconnected");
to = setTimeout(setupTcpClient, reConnect);
});
client.on('close', function() {
client.destroy();
node.log('closed');
to = setTimeout(setupTcpClient, reConnect);
});
node.on("input", function(msg) {
if (msg.payload != null) {
if (node.base64) { client.write(new Buffer(msg.payload,'base64')); }
else { client.write(msg.payload);}
}
node.log("connection lost to "+node.host+":"+node.port);
connected = false;
client.destroy();
if (!node.closing) {
reconnectTimeout = setTimeout(setupTcpClient,reconnectTime);
}
});
}
setupTcpClient();
node.on("input", function(msg) {
if (connected && msg.payload != null) {
if (Buffer.isBuffer(msg.payload)) {
client.write(msg.payload);
} else if (typeof msg.payload === "string" && node.base64) {
client.write(new Buffer(msg.payload,'base64'));
} else {
client.write(new Buffer(""+msg.payload));
}
}
});
this._close = function() {
this.closing = true;
client.end();
clearTimeout(to);
node.log('output stopped');
clearTimeout(reconnectTimeout);
}
}
else {
var connectedSockets = [];
var server = net.createServer(function (socket) {
socket.on("connect",function() {
node.log("Connection from "+socket.remoteAddress);
});
node.on("input", function(msg) {
if (msg.payload != null) {
if (node.base64) { socket.write(new Buffer(msg.payload,'base64')); }
else { socket.write(msg.payload);}
}
});
var remoteDetails = socket.remoteAddress+":"+socket.remotePort;
node.log("connection from "+remoteDetails);
connectedSockets.push(socket);
socket.on('close',function() {
node.log("connection closed from "+remoteDetails);
connectedSockets.splice(connectedSockets.indexOf(socket),1);
});
});
node.on("input", function(msg) {
if (msg.payload != null) {
var buffer;
if (Buffer.isBuffer(msg.payload)) {
buffer = msg.payload;
} else if (typeof msg.payload === "string" && node.base64) {
buffer = new Buffer(msg.payload,'base64');
} else {
buffer = new Buffer(""+msg.payload);
}
for (var i = 0; i<connectedSockets.length;i+=1) {
connectedSockets[i].write(buffer);
}
}
});
server.listen(node.port);
node.log('socket output on port '+node.port);
node.log('listening on port '+node.port);
this._close = function() {
server.close();
node.log('output stopped');
node.log('stopped listening on port '+node.port);
}
}
}

View File

@ -0,0 +1,114 @@
<!--
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.
-->
<script type="text/x-red" data-template-name="leveldbase">
<div class="form-row">
<label for="node-config-input-db"><i class="icon-briefcase"></i> Database</label>
<input type="text" id="node-config-input-db" placeholder="database path/name">
</div>
</script>
<script type="text/javascript">
RED.nodes.registerType('leveldbase',{
category: 'config',
defaults: {
db: {value:"",required:true}
},
label: function() {
return this.db;
}
});
</script>
<script type="text/x-red" data-template-name="leveldb in">
<div class="form-row node-input-level">
<label for="node-input-level"><i class="icon-briefcase"></i> Database</label>
<input type="text" id="node-input-level">
</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="leveldb in">
<p>Uses <a href="https://code.google.com/p/leveldb/" target="_new"><i>LevelDB</i></a> for a simple key value pair database.</p>
<p>Use this node to <b>get</b>, or retrieve the data already saved in the database.</p>
<p><b>msg.topic</b> must hold the <i>key</i> for the database, and the result is returned in <b>msg.payload</b>.</p>
</script>
<script type="text/javascript">
RED.nodes.registerType('leveldb in',{
category: 'storage-input',
color:"#dbb84d",
defaults: {
level: {type:"leveldbase",required:true},
name: {value:""}
},
inputs:1,
outputs:1,
icon: "leveldb.png",
label: function() {
var levelNode = RED.nodes.node(this.level);
return this.name||(levelNode?levelNode.label():"leveldb");
},
labelStyle: function() {
return this.name?"node_label_italic":"";
}
});
</script>
<script type="text/x-red" data-template-name="leveldb out">
<div class="form-row node-input-level">
<label for="node-input-level"><i class="icon-briefcase"></i> Database</label>
<input type="text" id="node-input-level">
</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="leveldb out">
<p>Uses <a href="https://code.google.com/p/leveldb/" target="_new"><i>LevelDB</i></a> for a simple key value pair database.</p>
<p>Use this node to <b>put</b> (save) the <b>msg.payload</b> to the named database file, using <b>msg.topic</b> as the key.</p>
<p>To <b>delete</b> information do a <b>put</b> to the required <b>msg.topic</b> (key) with a <b>msg.payload</b> of <b><i>null</i></b>.</p>
</script>
<script type="text/javascript">
RED.nodes.registerType('leveldb out',{
category: 'storage-output',
color:"#dbb84d",
defaults: {
level: {type:"leveldbase",required:true},
op: {value:"put",required:true},
name: {value:""}
},
inputs:1,
outputs:0,
icon: "leveldb.png",
align: "right",
label: function() {
var levelNode = RED.nodes.node(this.level);
return this.name||(levelNode?levelNode.label():"leveldb");
},
labelStyle: function() {
return this.name?"node_label_italic":"";
}
});
</script>

View File

@ -0,0 +1,90 @@
/**
* 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 lvldb = require('leveldb');
function LevelNode(n) {
RED.nodes.createNode(this,n);
this.dbname = n.db;
lvldb.open(this.dbname, { create_if_missing: true }, onOpen);
var node = this;
function onOpen(err, db) {
if (err) node.error(err);
node.db = db;
}
}
RED.nodes.registerType("leveldbase",LevelNode);
function LevelDBNodeIn(n) {
RED.nodes.createNode(this,n);
this.level = n.level;
this.op = n.op;
this.levelConfig = RED.nodes.getNode(this.level);
if (this.levelConfig) {
var node = this;
node.on("input", function(msg) {
if (typeof msg.topic === 'string') {
node.levelConfig.db.get(msg.topic, function(err, value) {
if (err) node.error(err);
msg.payload = JSON.parse(value);
delete msg.cmd;
node.send(msg);
});
}
else {
if (typeof msg.topic !== 'string') node.error("msg.topic (the key is not defined");
}
});
}
else {
this.error("LevelDB database name not configured");
}
}
RED.nodes.registerType("leveldb in",LevelDBNodeIn);
function LevelDBNodeOut(n) {
RED.nodes.createNode(this,n);
this.level = n.level;
this.levelConfig = RED.nodes.getNode(this.level);
if (this.levelConfig) {
var node = this;
node.on("input", function(msg) {
if (typeof msg.topic === 'string') {
console.log(msg);
if (msg.payload === null) {
node.levelConfig.db.del(msg.topic);
}
else {
node.levelConfig.db.put(msg.topic, JSON.stringify(msg.payload), function(err) {
if (err) node.error(err);
});
}
}
else {
if (typeof msg.topic !== 'string') node.error("msg.topic (the key is not defined");
}
});
}
else {
this.error("LevelDB database name not configured");
}
}
RED.nodes.registerType("leveldb out",LevelDBNodeOut);

BIN
public/icons/leveldb.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -96,7 +96,7 @@ RED.editor = function() {
valid = value !== "";
}
if (valid && "validate" in node._def.defaults[property]) {
valid = node._def.defaults[property].validate(value);
valid = node._def.defaults[property].validate.call(node,value);
}
if (valid && node._def.defaults[property].type && RED.nodes.getType(node._def.defaults[property].type)) {
valid = (value != "_ADD_");

View File

@ -16,14 +16,22 @@
RED.notify = function() {
var currentNotifications = [];
var c = 0;
return function(msg,type) {
while (currentNotifications.length > 4) {
var n = currentNotifications[0];
window.clearTimeout(n.id);
n.slideup();
return function(msg,type,fixed) {
if (currentNotifications.length > 4) {
var ll = currentNotifications.length;
for (var i = 0;ll > 4 && i<currentNotifications.length;i+=1) {
var n = currentNotifications[i];
if (!n.fixed) {
window.clearTimeout(n.timeoutid);
n.close();
ll -= 1;
}
}
}
var n = document.createElement("div");
n.id="red-notification-"+c;
n.className = "alert";
n.fixed = fixed;
if (type) {
n.className = "alert alert-"+type;
}
@ -31,18 +39,21 @@ RED.notify = function() {
n.innerHTML = msg;
$("#notifications").append(n);
$(n).slideDown(300);
var slideup = function() {
n.close = function() {
var nn = n;
return function() {
currentNotifications.shift();
currentNotifications.splice(currentNotifications.indexOf(nn),1);
$(nn).slideUp(300, function() {
nn.parentNode.removeChild(nn);
});
};
}();
var id = window.setTimeout(slideup,3000);
currentNotifications.push({id:id,slideup:slideup,c:c});
if (!fixed) {
n.timeoutid = window.setTimeout(n.close,3000);
}
currentNotifications.push(n);
c+=1;
return n;
}
}();

View File

@ -75,6 +75,7 @@ RED.sidebar = function() {
$("#main-container").removeClass("sidebar-closed");
}
}
toggleSidebar();
function addTab(title,content) {
var tab = document.createElement("li");

View File

@ -236,7 +236,10 @@ a.brand img {
}
.node_label_italic {
font-style:italic;
font-style: italic;
}
.node_label_white {
fill: #eee !important;
}
.node_label {
stroke-width: 0;

View File

@ -98,6 +98,7 @@ var node_type_registry = (function() {
if (! node_configs[configFilename]) {
node_configs[configFilename] = fs.readFileSync(configFilename,'utf8');
}
events.emit("type-registered",type);
} else {
util.log("["+type+"] missing template file: "+configFilename);
}
@ -106,9 +107,6 @@ var node_type_registry = (function() {
get: function(type) {
return node_types[type];
},
registerNodeConfig: function(type,config) {
node_configs[type] = config;
},
getNodeConfigs: function() {
var result = "";
for (var nt in node_configs) {
@ -256,33 +254,75 @@ module.exports.load = function() {
loadNodes("nodes");
events.emit("nodes-loaded");
//events.emit("nodes-loaded");
}
var activeConfig = null;
var missingTypes = [];
events.on('type-registered',function(type) {
if (missingTypes.length > 0) {
var i = missingTypes.indexOf(type);
if (i != -1) {
missingTypes.splice(i,1);
util.log("[red] Missing type registered: "+type);
}
if (missingTypes.length == 0) {
parseConfig();
}
}
});
module.exports.getNode = function(nid) {
return registry.get(nid);
}
module.exports.parseConfig = function(conf) {
module.exports.setConfig = function(conf) {
if (activeConfig&&activeConfig.length > 0) {
util.log("[red] Stopping flows");
}
registry.clear();
activeConfig = conf;
parseConfig();
}
var parseConfig = function() {
missingTypes = [];
for (var i in activeConfig) {
var type = activeConfig[i].type;
var nt = node_type_registry.get(type);
if (!nt && missingTypes.indexOf(type) == -1) {
missingTypes.push(type);
}
};
if (missingTypes.length > 0) {
util.log("[red] Waiting for missing types to be registered:");
for (var i in missingTypes) {
util.log("[red] - "+missingTypes[i]);
}
return;
}
util.log("[red] Starting flows");
events.emit("nodes-starting");
for (var i in conf) {
for (var i in activeConfig) {
var nn = null;
var nt = node_type_registry.get(conf[i].type);
var nt = node_type_registry.get(activeConfig[i].type);
if (nt) {
try {
nn = new nt(conf[i]);
nn = new nt(activeConfig[i]);
}
catch (err) {
util.log("[red] "+conf[i].type+" : "+err);
util.log("[red] "+activeConfig[i].type+" : "+err);
}
}
// console.log(nn);
if (nn == null) {
util.log("[red] unknown type: "+conf[i].type);
util.log("[red] unknown type: "+activeConfig[i].type);
}
}

View File

@ -60,7 +60,7 @@ function createServer(_server,settings) {
if(err) {
util.log(err);
} else {
redNodes.parseConfig(JSON.parse(fullBody));
redNodes.setConfig(JSON.parse(fullBody));
}
});
});
@ -79,13 +79,12 @@ function createServer(_server,settings) {
util.log('or any other errors are resolved');
util.log("------------------------------------------");
util.log("[red] Loading workspace flow : "+rulesfile);
fs.exists(rulesfile, function (exists) {
if (exists) {
util.log("[red] Loading workspace flow : "+rulesfile);
fs.readFile(rulesfile,'utf8',function(err,data) {
redNodes.parseConfig(JSON.parse(data));
redNodes.setConfig(JSON.parse(data));
});
}
});

View File

@ -17,6 +17,7 @@ module.exports = {
uiPort: 1880,
mqttReconnectTime: 15000,
serialReconnectTime: 15000,
debugMaxLength: 1000,
// 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')
@ -33,7 +34,7 @@ module.exports = {
// key: fs.readFileSync('privatekey.pem'),
// cert: fs.readFileSync('certificate.pem')
//},
// Anything in this hash is globally available to all functions.
// It is accessed as context.global.
// eg: