summaryrefslogtreecommitdiffstats
path: root/dgbuilder/core_nodes
diff options
context:
space:
mode:
Diffstat (limited to 'dgbuilder/core_nodes')
-rw-r--r--dgbuilder/core_nodes/analysis/72-sentiment.html49
-rw-r--r--dgbuilder/core_nodes/analysis/72-sentiment.js33
-rw-r--r--dgbuilder/core_nodes/core/20-inject.html437
-rw-r--r--dgbuilder/core_nodes/core/20-inject.js97
-rw-r--r--dgbuilder/core_nodes/core/58-debug.html248
-rw-r--r--dgbuilder/core_nodes/core/58-debug.js114
-rw-r--r--dgbuilder/core_nodes/core/75-exec.html68
-rw-r--r--dgbuilder/core_nodes/core/75-exec.js84
-rw-r--r--dgbuilder/core_nodes/core/80-function.html110
-rw-r--r--dgbuilder/core_nodes/core/80-function.js79
-rw-r--r--dgbuilder/core_nodes/core/80-template.html102
-rw-r--r--dgbuilder/core_nodes/core/80-template.js61
-rw-r--r--dgbuilder/core_nodes/core/89-delay.html167
-rw-r--r--dgbuilder/core_nodes/core/89-delay.js171
-rw-r--r--dgbuilder/core_nodes/core/89-trigger.html130
-rw-r--r--dgbuilder/core_nodes/core/89-trigger.js91
-rw-r--r--dgbuilder/core_nodes/core/90-comment.html86
-rw-r--r--dgbuilder/core_nodes/core/90-comment.js23
-rw-r--r--dgbuilder/core_nodes/core/98-unknown.html49
-rw-r--r--dgbuilder/core_nodes/core/98-unknown.js23
-rw-r--r--dgbuilder/core_nodes/deprecated/61-imap.html56
-rw-r--r--dgbuilder/core_nodes/deprecated/61-imap.js139
-rw-r--r--dgbuilder/core_nodes/deprecated/73-parsexml.html53
-rw-r--r--dgbuilder/core_nodes/deprecated/73-parsexml.js47
-rw-r--r--dgbuilder/core_nodes/deprecated/74-js2xml.html51
-rw-r--r--dgbuilder/core_nodes/deprecated/74-js2xml.js39
-rw-r--r--dgbuilder/core_nodes/deprecated/90-httpget.html61
-rw-r--r--dgbuilder/core_nodes/deprecated/90-httpget.js53
-rw-r--r--dgbuilder/core_nodes/hardware/35-arduino.html171
-rw-r--r--dgbuilder/core_nodes/hardware/35-arduino.js160
-rw-r--r--dgbuilder/core_nodes/hardware/36-rpi-gpio.html182
-rw-r--r--dgbuilder/core_nodes/hardware/36-rpi-gpio.js185
-rw-r--r--dgbuilder/core_nodes/io/10-mqtt.html157
-rw-r--r--dgbuilder/core_nodes/io/10-mqtt.js119
-rw-r--r--dgbuilder/core_nodes/io/21-httpin.html254
-rw-r--r--dgbuilder/core_nodes/io/21-httpin.js241
-rw-r--r--dgbuilder/core_nodes/io/22-websocket.html163
-rw-r--r--dgbuilder/core_nodes/io/22-websocket.js185
-rw-r--r--dgbuilder/core_nodes/io/23-watch.html57
-rw-r--r--dgbuilder/core_nodes/io/23-watch.js51
-rw-r--r--dgbuilder/core_nodes/io/25-serial.html265
-rw-r--r--dgbuilder/core_nodes/io/25-serial.js310
-rw-r--r--dgbuilder/core_nodes/io/31-tcpin.html299
-rw-r--r--dgbuilder/core_nodes/io/31-tcpin.js472
-rw-r--r--dgbuilder/core_nodes/io/32-udp.html212
-rw-r--r--dgbuilder/core_nodes/io/32-udp.js171
-rw-r--r--dgbuilder/core_nodes/io/lib/mqtt.js254
-rw-r--r--dgbuilder/core_nodes/io/lib/mqttConnectionPool.js128
-rw-r--r--dgbuilder/core_nodes/logic/10-switch.html198
-rw-r--r--dgbuilder/core_nodes/logic/10-switch.js78
-rw-r--r--dgbuilder/core_nodes/logic/15-change.html139
-rw-r--r--dgbuilder/core_nodes/logic/15-change.js74
-rw-r--r--dgbuilder/core_nodes/logic/16-range.html81
-rw-r--r--dgbuilder/core_nodes/logic/16-range.js48
-rw-r--r--dgbuilder/core_nodes/parsers/70-CSV.html123
-rw-r--r--dgbuilder/core_nodes/parsers/70-CSV.js157
-rw-r--r--dgbuilder/core_nodes/parsers/70-HTML.html73
-rw-r--r--dgbuilder/core_nodes/parsers/70-HTML.js60
-rw-r--r--dgbuilder/core_nodes/parsers/70-JSON.html47
-rw-r--r--dgbuilder/core_nodes/parsers/70-JSON.js46
-rw-r--r--dgbuilder/core_nodes/parsers/70-XML.html48
-rw-r--r--dgbuilder/core_nodes/parsers/70-XML.js46
-rw-r--r--dgbuilder/core_nodes/social/27-twitter.html223
-rw-r--r--dgbuilder/core_nodes/social/27-twitter.js347
-rw-r--r--dgbuilder/core_nodes/social/32-feedparse.html57
-rw-r--r--dgbuilder/core_nodes/social/32-feedparse.js71
-rw-r--r--dgbuilder/core_nodes/social/61-email.html189
-rw-r--r--dgbuilder/core_nodes/social/61-email.js246
-rw-r--r--dgbuilder/core_nodes/social/91-irc.html206
-rw-r--r--dgbuilder/core_nodes/social/91-irc.js237
-rw-r--r--dgbuilder/core_nodes/storage/28-tail.html58
-rw-r--r--dgbuilder/core_nodes/storage/28-tail.js69
-rw-r--r--dgbuilder/core_nodes/storage/50-file.html110
-rw-r--r--dgbuilder/core_nodes/storage/50-file.js93
-rw-r--r--dgbuilder/core_nodes/storage/65-redisout.html105
-rw-r--r--dgbuilder/core_nodes/storage/65-redisout.js107
-rw-r--r--dgbuilder/core_nodes/storage/66-mongodb.html231
-rw-r--r--dgbuilder/core_nodes/storage/66-mongodb.js233
78 files changed, 10557 insertions, 0 deletions
diff --git a/dgbuilder/core_nodes/analysis/72-sentiment.html b/dgbuilder/core_nodes/analysis/72-sentiment.html
new file mode 100644
index 00000000..c33b873b
--- /dev/null
+++ b/dgbuilder/core_nodes/analysis/72-sentiment.html
@@ -0,0 +1,49 @@
+<!--
+ 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="sentiment">
+ <div class="form-row">
+ <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
+ <input type="text" id="node-input-name" placeholder="Name">
+ </div>
+</script>
+
+<script type="text/x-red" data-help-name="sentiment">
+ <p>Analyses the <b>msg.payload</b> and adds a <b>msg.sentiment</b> object that contains the resulting AFINN-111 sentiment score as <b>msg.sentiment.score</b>.</p>
+ <p>A score greater than zero is positive and less than zero is negative.</p>
+ <p>The score typically ranges from -5 to +5, but can go higher and lower.</p>
+ <p>An object of word score overrides can be supplied as <b>msg.overrides</b>.</p>
+ <p>See <a href="https://github.com/thisandagain/sentiment/blob/master/README.md" target="_new">the Sentiment docs here</a>.</p>
+</script>
+
+<script type="text/javascript">
+ RED.nodes.registerType('sentiment',{
+ category: 'analysis-function',
+ color:"#E6E0F8",
+ defaults: {
+ name: {value:""},
+ },
+ inputs:1,
+ outputs:1,
+ icon: "arrow-in.png",
+ label: function() {
+ return this.name||"sentiment";
+ },
+ labelStyle: function() {
+ return this.name?"node_label_italic":"";
+ }
+ });
+</script>
diff --git a/dgbuilder/core_nodes/analysis/72-sentiment.js b/dgbuilder/core_nodes/analysis/72-sentiment.js
new file mode 100644
index 00000000..747e079c
--- /dev/null
+++ b/dgbuilder/core_nodes/analysis/72-sentiment.js
@@ -0,0 +1,33 @@
+/**
+ * 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.
+ **/
+
+module.exports = function(RED) {
+ "use strict";
+ var sentiment = require('sentiment');
+
+ function SentimentNode(n) {
+ RED.nodes.createNode(this,n);
+ var node = this;
+
+ this.on("input", function(msg) {
+ sentiment(msg.payload, msg.overrides || null, function (err, result) {
+ msg.sentiment = result;
+ node.send(msg);
+ });
+ });
+ }
+ RED.nodes.registerType("sentiment",SentimentNode);
+}
diff --git a/dgbuilder/core_nodes/core/20-inject.html b/dgbuilder/core_nodes/core/20-inject.html
new file mode 100644
index 00000000..38aa6efe
--- /dev/null
+++ b/dgbuilder/core_nodes/core/20-inject.html
@@ -0,0 +1,437 @@
+<!--
+ 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="inject">
+ <div class="form-row node-input-payload">
+ <label for="node-input-payloadType"><i class="fa fa-envelope"></i> Payload</label>
+ <select id="node-input-payloadType" style="width:125px !important">
+ <option value="date">timestamp</option>
+ <option value="none">blank</option>
+ <option value="string">string</option>
+ </select>
+ </div>
+
+ <div class="form-row" id="node-input-row-payload">
+ <label for="node-input-payload"></label>
+ <input type="text" id="node-input-payload" placeholder="payload">
+ </div>
+
+ <div class="form-row">
+ <label for="node-input-topic"><i class="fa fa-tasks"></i> Topic</label>
+ <input type="text" id="node-input-topic" placeholder="topic">
+ </div>
+
+ <div class="form-row">
+ <label for=""><i class="fa fa-repeat"></i> Repeat</label>
+ <select id="inject-time-type-select" style="width: 288px"><option value="none">none</option><option value="interval">interval</option><option value="interval-time">interval between times</option><option value="time">at a specific time</option></select>
+ <input type="hidden" id="node-input-repeat" placeholder="payload">
+ <input type="hidden" id="node-input-crontab" placeholder="payload">
+ </div>
+
+ <div class="form-row inject-time-row hidden" id="inject-time-row-interval">
+ every <input id="inject-time-interval-count" class="inject-time-count" value="1"></input>
+ <select style="width: 100px" id="inject-time-interval-units"><option value="s">seconds</option><option value="m">minutes</option><option value="h">hours</option></select><br/>
+ <!-- on <select disabled id="inject-time-interval-days" class="inject-time-days"></select> -->
+ </div>
+
+ <div class="form-row inject-time-row hidden" id="inject-time-row-interval-time">
+ at every <select style="width: 90px" id="inject-time-interval-time-units" class="inject-time-int-count" value="1">
+ <option value="1">1</option>
+ <option value="2">2</option>
+ <option value="3">3</option>
+ <option value="4">4</option>
+ <option value="5">5</option>
+ <option value="6">6</option>
+ <option value="10">10</option>
+ <option value="12">12</option>
+ <option value="15">15</option>
+ <option value="20">20</option>
+ <option value="30">30</option>
+ <option value="0">60</option>
+ </select> minutes<br/>
+ between <select id="inject-time-interval-time-start" class="inject-time-times"></select>
+ and <select id="inject-time-interval-time-end" class="inject-time-times"></select><br/>
+ on <select id="inject-time-interval-time-days" class="inject-time-days"></select>
+ </div>
+
+ <div class="form-row inject-time-row hidden" id="inject-time-row-time">
+ at <input id="inject-time-time" value="12:00"></input><br/>
+ on <select id="inject-time-time-days" class="inject-time-days"></select>
+ </div>
+
+ <div class="form-row" id="node-once">
+ <label>&nbsp;</label>
+ <input type="checkbox" id="node-input-once" style="display: inline-block; width: auto; vertical-align: top;">
+ <label for="node-input-once" style="width: 70%;">Fire once at start ?</label>
+ </div>
+
+ <div class="form-row">
+ <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
+ <input type="text" id="node-input-name" placeholder="name">
+ </div>
+
+ <div class="form-tips"><b>Note:</b> "interval between times" and "at a specific time" will use cron.<br/>See info box for details.</div>
+</script>
+<style>
+ .inject-time-row {
+ padding-left: 110px;
+ }
+ .inject-time-row select {
+ margin: 3px 0;
+ }
+ .inject-time-days {
+ width: 262px;
+ }
+ .inject-time-times {
+ width: 90px;
+ }
+ .inject-time-row > .ui-spinner {
+ height: 28px;
+ margin: 3px 0;
+ border-color: rgb(204, 204, 204);
+ }
+ #inject-time-time {
+ margin-top: 3px;
+ width: 75px;
+ }
+ .inject-time-count {
+ width: 40px !important;
+ }
+</style>
+<script type="text/x-red" data-help-name="inject">
+ <p>Pressing the button on the left side of the node allows a message on a topic to be injected into the flow. This is mainly for test purposes.</p>
+ <p>If no payload is specified the payload is set to the current time in millisecs since 1970. This allows subsequent functions to perform time based actions.</p>
+ <p>The repeat function does what it says on the tin and continuously sends the payload every x seconds.</p>
+ <p>The Fire once at start option actually waits 50mS before firing to give other nodes a chance to instantiate properly.</p>
+ <p><b>Note: </b>"Interval between times" and "at a specific time" will use cron. This means that 20 minutes will be at the next hour, 20 minutes past and 40 minutes past - not in 20 minutes time.
+ If you want every 20 minutes from now - use the basic "interval" option.</p>
+</script>
+
+<script type="text/javascript">
+ RED.nodes.registerType('inject',{
+ category: 'input',
+ color:"#a6bbcf",
+ defaults: {
+ name: {value:""},
+ topic: {value:""},
+ payload: {value:""},
+ payloadType: {value:"date"},
+ repeat: {value:""},
+ crontab: {value:""},
+ once: {value:false}
+ },
+ inputs:0,
+ outputs:1,
+ icon: "inject.png",
+ label: function() {
+ if (this.payloadType === "string") {
+ return this.name||this.topic||this.payload||"inject";
+ }
+ else { return this.name||this.topic||"inject"; }
+ },
+ labelStyle: function() {
+ return this.name?"node_label_italic":"";
+ },
+ oneditprepare: function() {
+ $("#inject-time-type-select").change(function() {
+ $("#node-input-crontab").val('');
+ var id = $("#inject-time-type-select option:selected").val();
+ $(".inject-time-row").hide();
+ $("#inject-time-row-"+id).show();
+ if ((id == "none") || (id == "interval")) {
+ $("#node-once").show();
+ }
+ else {
+ $("#node-once").hide();
+ $("#node-input-once").prop('checked', false);
+ }
+ });
+
+ var days = [
+ {v:"*",t:"every day"},
+ {v:"1-5",t:"Mondays to Fridays"},
+ {v:"0,6",t:"Saturdays and Sundays"},
+ {v:"1",t:"Mondays"},
+ {v:"2",t:"Tuesdays"},
+ {v:"3",t:"Wednesdays"},
+ {v:"4",t:"Thursdays"},
+ {v:"5",t:"Fridays"},
+ {v:"6",t:"Saturdays"},
+ {v:"0",t:"Sundays"}
+ ];
+
+ $(".inject-time-days").each(function() {
+ for (var d in days) {
+ $(this).append($("<option></option>").val(days[d].v).text(days[d].t));
+ }
+ });
+
+ $(".inject-time-times").each(function() {
+ for (var i=0;i<24;i++) {
+ var l = (i<10?"0":"")+i+":00";
+ $(this).append($("<option></option>").val(i).text(l));
+ }
+ });
+
+ $(".inject-time-count").spinner({
+ //max:60,
+ min:1
+ });
+
+ $("#inject-time-interval-units").change(function() {
+ var units = $("#inject-time-interval-units option:selected").val();
+ //$("#inject-time-interval-days").prop("disabled",(units == "s")?"disabled":false);
+ //$(".inject-time-count").spinner("option","max",(units == "h")?24:60);
+ });
+
+ $.widget( "ui.injecttimespinner", $.ui.spinner, {
+ options: {
+ // seconds
+ step: 60 * 1000,
+ // hours
+ page: 60
+ },
+ _parse: function( value ) {
+ if ( typeof value === "string" ) {
+ // already a timestamp
+ if ( Number( value ) == value ) {
+ return Number( value );
+ }
+ var p = value.split(":");
+ var offset = new Date().getTimezoneOffset();
+ return (((Number(p[0])+1)*60)+Number(p[1])+offset)*60*1000;
+ }
+ return value;
+ },
+ _format: function( value ) {
+ var d = new Date(value);
+ var h = d.getHours();
+ var m = d.getMinutes();
+ return ((h < 10)?"0":"")+h+":"+((m < 10)?"0":"")+m;
+ }
+ });
+
+ $("#inject-time-time").injecttimespinner();
+
+ var repeattype = "none";
+ if (this.repeat != "" && this.repeat != 0) {
+ repeattype = "interval";
+ var r = "s";
+ var c = this.repeat;
+ if (this.repeat % 60 === 0) { r = "m"; c = c/60; }
+ if (this.repeat % 1440 === 0) { r = "h"; c = c/24; }
+ $("#inject-time-interval-count").val(c);
+ $("#inject-time-interval-units").val(r);
+ //$("#inject-time-interval-units option").filter(function() {return $(this).val() == "s";}).attr('selected',true);
+ $("#inject-time-interval-days").prop("disabled","disabled");
+ } else if (this.crontab) {
+ var cronparts = this.crontab.split(" ");
+ var days = cronparts[4];
+ if (!isNaN(cronparts[0]) && !isNaN(cronparts[1])) {
+ repeattype = "time";
+ // Fixed time
+ var time = cronparts[1]+":"+cronparts[0];
+ $("#inject-time-time").val(time);
+ $("#inject-time-type-select option").filter(function() {return $(this).val() == "s";}).attr('selected',true);
+ $("#inject-time-time-days option").filter(function() {return $(this).val() == days;}).attr('selected',true);
+ }
+ //else if (cronparts[0] == "0") {
+ // // interval - hours
+ // var hours = cronparts[1].slice(2);
+ // repeattype = "interval";
+ // $("#inject-time-interval-days").prop("disabled",false);
+ // $("#inject-time-interval-days option").filter(function() {return $(this).val() == days;}).attr('selected',true);
+ // $("#inject-time-interval-count").val(hours)
+ // $("#inject-time-interval-units option").filter(function() {return $(this).val() == "h";}).attr('selected',true);
+ //} else if (cronparts[1] == "*") {
+ // // interval - minutes
+ // var minutes = cronparts[0].slice(2);
+ // repeattype = "interval";
+ // $("#inject-time-interval-days").prop("disabled",false);
+ // $("#inject-time-interval-days option").filter(function() {return $(this).val() == days;}).attr('selected',true);
+ // $("#inject-time-interval-count").val(minutes)
+ // $("#inject-time-interval-units option").filter(function() {return $(this).val() == "m";}).attr('selected',true);
+ //}
+ else {
+ repeattype = "interval-time";
+ // interval - time period
+ var minutes = cronparts[0].slice(2);
+ if (minutes === "") { minutes = "0"; }
+ $("#inject-time-interval-time-units").val(minutes);
+ $("#inject-time-interval-time-days option").filter(function() {return $(this).val() == days;}).attr('selected',true);
+ var time = cronparts[1];
+ var timeparts = time.split(",");
+ var start;
+ var end;
+ if (timeparts.length == 1) {
+ // 0 or 0-10
+ var hours = timeparts[0].split("-");
+ if (hours.length == 1) {
+ if (hours[0] === "") {
+ start = "0";
+ end = "0";
+ }
+ else {
+ start = hours[0];
+ end = Number(hours[0])+1;
+ }
+ } else {
+ start = hours[0];
+ end = (Number(hours[1])+1)%24;
+ }
+ } else {
+ // 23,0 or 17-23,0-10 or 23,0-2 or 17-23,0
+ var startparts = timeparts[0].split("-");
+ start = startparts[0];
+
+ var endparts = timeparts[1].split("-");
+ if (endparts.length == 1) {
+ end = Number(endparts[0])+1;
+ } else {
+ end = Number(endparts[1])+1;
+ }
+ }
+ $("#inject-time-interval-time-start option").filter(function() {return $(this).val() == start;}).attr('selected',true);
+ $("#inject-time-interval-time-end option").filter(function() {return $(this).val() == end;}).attr('selected',true);
+
+ }
+ } else {
+ $("#inject-time-type-select option").filter(function() {return $(this).val() == "none";}).attr('selected',true);
+ }
+
+ $(".inject-time-row").hide();
+ $("#inject-time-type-select option").filter(function() {return $(this).val() == repeattype;}).attr('selected',true);
+ $("#inject-time-row-"+repeattype).show();
+
+ if (this.payloadType == null) {
+ if (this.payload == "") {
+ this.payloadType = "date";
+ } else {
+ this.payloadType = "string";
+ }
+ }
+
+ $("#node-input-payloadType").change(function() {
+ var id = $("#node-input-payloadType option:selected").val();
+ if (id == "string") {
+ $("#node-input-row-payload").show();
+ } else {
+ $("#node-input-row-payload").hide();
+ }
+ });
+ $("#node-input-payloadType").val(this.payloadType);
+ $("#node-input-payloadType").change();
+ $("#inject-time-type-select").change();
+
+ },
+ oneditsave: function() {
+ var repeat = "";
+ var crontab = "";
+ var type = $("#inject-time-type-select option:selected").val();
+ if (type == "none") {
+ // nothing
+ } else if (type == "interval") {
+ var count = $("#inject-time-interval-count").val();
+ var units = $("#inject-time-interval-units option:selected").val();
+ var days = $("#inject-time-interval-days option:selected").val();
+ if (units == "s") {
+ repeat = count;
+ } else {
+ if (units == "m") {
+ //crontab = "*/"+count+" * * * "+days;
+ repeat = count * 60;
+ } else if (units == "h") {
+ //crontab = "0 */"+count+" * * "+days;
+ repeat = count * 60 * 24;
+ }
+ }
+ } else if (type == "interval-time") {
+ var count = $("#inject-time-interval-time-units").val();
+ var startTime = Number($("#inject-time-interval-time-start option:selected").val());
+ var endTime = Number($("#inject-time-interval-time-end option:selected").val());
+ var days = $("#inject-time-interval-time-days option:selected").val();
+ var timerange = "*";
+ if (startTime == endTime) {
+ //TODO: invalid
+ repeat = "";
+ crontab = "";
+ } else if (endTime == 0) {
+ timerange = startTime+"-23";
+ } else if (startTime+1 < endTime) {
+ timerange = startTime+"-"+(endTime-1);
+ } else if (startTime+1 == endTime) {
+ timerange = startTime;
+ } else {
+ var startpart = "";
+ var endpart = "";
+ if (startTime == 23) {
+ startpart = "23";
+ } else {
+ startpart = startTime+"-23";
+ }
+ if (endTime == 1) {
+ endpart = "0";
+ } else {
+ endpart = "0-"+(endTime-1);
+ }
+ timerange = startpart+","+endpart;
+ }
+ repeat = "";
+ if (count === "0") {
+ crontab = count+" "+timerange+" * * "+days;
+ }
+ else {
+ crontab = "*/"+count+" "+timerange+" * * "+days;
+ }
+ } else if (type == "time") {
+ var time = $("#inject-time-time").val();
+ var days = $("#inject-time-time-days option:selected").val();
+ var parts = time.split(":");
+ repeat = "";
+ crontab = parts[1]+" "+parts[0]+" * * "+days;
+ }
+
+ $("#node-input-repeat").val(repeat);
+ $("#node-input-crontab").val(crontab);
+
+ },
+ button: {
+ onclick: function() {
+ var label = (this.name||this.payload).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");
+ if (this.payloadType === "date") { label = "timestamp"; }
+ if (this.payloadType === "none") { label = "blank"; }
+ d3.xhr("inject/"+this.id).post(function(err,resp) {
+ if (err) {
+ if (err.status == 404) {
+ RED.notify("<strong>Error</strong>: inject node not deployed","error");
+ } else if (err.status == 500) {
+ RED.notify("<strong>Error</strong>: inject failed, see log for details.","error");
+ } else if (err.status == 0) {
+ RED.notify("<strong>Error</strong>: no response from server","error");
+ } else {
+ RED.notify("<strong>Error</strong>: unexpected error: ("+err.status+")"+err.response,"error");
+ }
+ } else if (resp.status == 200) {
+ RED.notify("Successfully injected: "+label,"success");
+ } else {
+ RED.notify("<strong>Error</strong>: unexpected response: ("+resp.status+") "+resp.response,"error");
+ }
+ });
+ }
+ }
+ });
+
+</script>
diff --git a/dgbuilder/core_nodes/core/20-inject.js b/dgbuilder/core_nodes/core/20-inject.js
new file mode 100644
index 00000000..dff0fb65
--- /dev/null
+++ b/dgbuilder/core_nodes/core/20-inject.js
@@ -0,0 +1,97 @@
+/**
+ * Copyright 2013, 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.
+ **/
+
+module.exports = function(RED) {
+ var cron = require("cron");
+
+ function InjectNode(n) {
+ RED.nodes.createNode(this,n);
+ this.topic = n.topic;
+ this.payload = n.payload;
+ this.payloadType = n.payloadType;
+ this.repeat = n.repeat;
+ this.crontab = n.crontab;
+ this.once = n.once;
+ var node = this;
+ this.interval_id = null;
+ this.cronjob = null;
+
+ if (this.repeat && !isNaN(this.repeat) && this.repeat > 0) {
+ this.repeat = this.repeat * 1000;
+ this.log("repeat = "+this.repeat);
+ this.interval_id = setInterval( function() {
+ node.emit("input",{});
+ }, this.repeat );
+ } else if (this.crontab) {
+ if (cron) {
+ this.log("crontab = "+this.crontab);
+ this.cronjob = new cron.CronJob(this.crontab,
+ function() {
+ node.emit("input",{});
+ },
+ null,true);
+ } else {
+ this.error("'cron' module not found");
+ }
+ }
+
+ if (this.once) {
+ setTimeout( function(){ node.emit("input",{}); }, 100);
+ }
+
+ this.on("input",function(msg) {
+ var msg = {topic:this.topic};
+ if ( (this.payloadType == null && this.payload == "") || this.payloadType == "date") {
+ msg.payload = Date.now();
+ } else if (this.payloadType == null || this.payloadType == "string") {
+ msg.payload = this.payload;
+ } else {
+ msg.payload = "";
+ }
+ this.send(msg);
+ msg = null;
+ });
+ }
+
+ RED.nodes.registerType("inject",InjectNode);
+
+ InjectNode.prototype.close = function() {
+ if (this.interval_id != null) {
+ clearInterval(this.interval_id);
+ this.log("inject: repeat stopped");
+ } else if (this.cronjob != null) {
+ this.cronjob.stop();
+ this.log("inject: cronjob stopped");
+ delete this.cronjob;
+ }
+ }
+
+ RED.httpAdmin.post("/inject/:id", function(req,res) {
+ var node = RED.nodes.getNode(req.params.id);
+ if (node != null) {
+ try {
+ node.receive();
+ res.send(200);
+ } catch(err) {
+ res.send(500);
+ node.error("Inject failed:"+err);
+ console.log(err.stack);
+ }
+ } else {
+ res.send(404);
+ }
+ });
+}
diff --git a/dgbuilder/core_nodes/core/58-debug.html b/dgbuilder/core_nodes/core/58-debug.html
new file mode 100644
index 00000000..04aa5078
--- /dev/null
+++ b/dgbuilder/core_nodes/core/58-debug.html
@@ -0,0 +1,248 @@
+<!--
+ 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="debug">
+ <div class="form-row">
+ <label for="node-input-complete"><i class="fa fa-list"></i> Output</label>
+ <select type="text" id="node-input-complete" style="display: inline-block; width: 250px; vertical-align: top;">
+ <option value="false">payload only</option>
+ <option value="true">complete msg object</option>
+ </select>
+ </div>
+ <div class="form-row">
+ <label for="node-input-console"><i class="fa fa-random"></i> to</label>
+ <select type="text" id="node-input-console" style="display: inline-block; width: 250px; vertical-align: top;">
+ <option value="false">debug tab</option>
+ <option value="true">debug tab and console</option>
+ </select>
+ </div>
+ <div class="form-row">
+ <label for="node-input-name"><i class="fa fa-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.
+ <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>
+ <p>If the payload is a buffer it will be stringified first for display and indicate that by saying "(Buffer) ".</p>
+ <p>Selecting any particular message will highlight (in red) the debug node that reported it. This is useful if you wire up multiple debug nodes.</p>
+ <p>Optionally can show the complete msg object - but the screen can get messy.</p>
+ <p>In addition any calls to node.warn or node.error will appear here.</p>
+</script>
+
+<script type="text/javascript">
+ RED.nodes.registerType('debug',{
+ category: 'output',
+ defaults: {
+ name: {value:""},
+ active: {value:true},
+ console: {value:"false"},
+ complete: {value:"false"}
+ },
+ label: function() {
+ return this.name||"debug";
+ },
+ labelStyle: function() {
+ return this.name?"node_label_italic":"";
+ },
+ color:"#87a980",
+ inputs:1,
+ outputs:0,
+ icon: "debug.png",
+ align: "right",
+ button: {
+ toggle: "active",
+ onclick: function() {
+ var label = this.name||"debug";
+ d3.xhr("debug/"+this.id+"/"+(this.active?"enable":"disable")).post(function(err,resp) {
+ if (err) {
+ if (err.status == 404) {
+ RED.notify("<strong>Error</strong>: debug node not deployed","error");
+ } else if (err.status == 0) {
+ RED.notify("<strong>Error</strong>: no response from server","error");
+ } else {
+ RED.notify("<strong>Error</strong>: unexpected error: ("+err.status+")"+err.response,"error");
+ }
+ } else if (resp.status == 200) {
+ RED.notify("Successfully activated: "+label,"success");
+ } else if (resp.status == 201) {
+ RED.notify("Successfully deactivated: "+label,"success");
+ } else {
+ RED.notify("<strong>Error</strong>: unexpected response: ("+resp.status+") "+resp.response,"error");
+ }
+ });
+ }
+ },
+ onpaletteadd: function() {
+ var content = document.createElement("div");
+ content.id = "tab-debug";
+
+ var toolbar = document.createElement("div");
+ toolbar.id = "debug-toolbar";
+ content.appendChild(toolbar);
+
+ toolbar.innerHTML = '<div class="btn-group pull-right"><a id="debug-tab-clear" title="clear log" class="btn btn-mini" href="#"><i class="fa fa-trash"></i></a></div> ';
+
+ var messages = document.createElement("div");
+ messages.id = "debug-content";
+ content.appendChild(messages);
+
+ RED.sidebar.addTab("debug",content);
+
+ function getTimestamp() {
+ var d = new Date();
+ return d.toLocaleString();
+ }
+
+ var sbc = document.getElementById("debug-content");
+
+ var messageCount = 0;
+ var that = this;
+ RED._debug = function(msg) {
+ that.handleDebugMessage("",{
+ name:"debug",
+ msg:msg
+ });
+ }
+
+ this.handleDebugMessage = function(t,o) {
+ var msg = document.createElement("div");
+ msg.onmouseover = function() {
+ msg.style.borderRightColor = "#999";
+ var n = RED.nodes.node(o.id);
+ if (n) {
+ n.highlighted = true;
+ n.dirty = true;
+ }
+ RED.view.redraw();
+ };
+ msg.onmouseout = function() {
+ msg.style.borderRightColor = "";
+ var n = RED.nodes.node(o.id);
+ if (n) {
+ n.highlighted = false;
+ n.dirty = true;
+ }
+ RED.view.redraw();
+ };
+ msg.onclick = function() {
+ var node = RED.nodes.node(o.id);
+ if (node) {
+ RED.view.showWorkspace(node.z);
+ }
+
+ };
+ var name = (o.name?o.name:o.id).toString().replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");
+ var topic = (o.topic||"").toString().replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");
+ var payload = (o.msg||"").toString().replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");
+ msg.className = 'debug-message'+(o.level?(' debug-message-level-'+o.level):'');
+ msg.innerHTML = '<span class="debug-message-date">'+getTimestamp()+'</span>'+
+ '<span class="debug-message-name">['+name+']</span>'+
+ (o.topic?'<span class="debug-message-topic">'+topic+'</span>':'')+
+ '<span class="debug-message-payload">'+payload+'</span>';
+ var atBottom = (sbc.scrollHeight-messages.offsetHeight-sbc.scrollTop) < 5;
+ messageCount++;
+ $(messages).append(msg);
+
+ if (messageCount > 200) {
+ $("#debug-content .debug-message:first").remove();
+ messageCount--;
+ }
+ if (atBottom) {
+ $(sbc).scrollTop(sbc.scrollHeight);
+ }
+ };
+ RED.comms.subscribe("debug",this.handleDebugMessage);
+
+ $("#debug-tab-clear").click(function() {
+ $(".debug-message").remove();
+ messageCount = 0;
+ RED.nodes.eachNode(function(node) {
+ node.highlighted = false;
+ node.dirty = true;
+ });
+ RED.view.redraw();
+ });
+ },
+ onpaletteremove: function() {
+ RED.comms.unsubscribe("debug",this.handleDebugMessage);
+ RED.sidebar.removeTab("debug");
+ delete RED._debug;
+ }
+ });
+</script>
+
+<style>
+ #debug-content {
+ position: absolute;
+ top: 30px;
+ bottom: 0px;
+ left:0px;
+ right: 0px;
+ overflow-y: scroll;
+ }
+ #debug-toolbar {
+ padding: 3px 10px;
+ height: 24px;
+ background: #f3f3f3;
+ }
+ .debug-message {
+ cursor: pointer;
+ border-bottom: 1px solid #eee;
+ border-left: 8px solid #eee;
+ border-right: 8px solid #eee;
+ padding: 2px;
+ }
+ .debug-message-date {
+ background: #fff;
+ font-size: 9px;
+ color: #aaa;
+ padding: 1px 5px 1px 1px;
+ }
+ .debug-message-topic {
+ display: block;
+ background: #fff;
+ padding: 1px 5px;
+ font-size: 9px;
+ color: #a66;
+ }
+ .debug-message-name {
+ background: #fff;
+ padding: 1px 5px;
+ font-size: 9px;
+ color: #aac;
+ }
+ .debug-message-payload {
+ display: block;
+ padding: 2px;
+ background: #fff;
+ }
+ .debug-message-level-log {
+ border-left-color: #eee;
+ border-right-color: #eee;
+ }
+ .debug-message-level-warn {
+ border-left-color: #ffdf9d;
+ border-right-color: #ffdf9d;
+ }
+ .debug-message-level-error {
+ border-left-color: #f99;
+ border-right-color: #f99;
+ }
+</style>
diff --git a/dgbuilder/core_nodes/core/58-debug.js b/dgbuilder/core_nodes/core/58-debug.js
new file mode 100644
index 00000000..7436bf2c
--- /dev/null
+++ b/dgbuilder/core_nodes/core/58-debug.js
@@ -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.
+ **/
+
+module.exports = function(RED) {
+ var util = require("util");
+ var events = require("events");
+ var debuglength = RED.settings.debugMaxLength||1000;
+ var useColors = false;
+ // util.inspect.styles.boolean = "red";
+
+ function DebugNode(n) {
+ RED.nodes.createNode(this,n);
+ this.name = n.name;
+ this.complete = n.complete;
+ this.console = n.console;
+ this.active = (n.active == null)||n.active;
+ var node = this;
+
+ this.on("input",function(msg) {
+ if (this.complete == "true") { // debug complete msg object
+ if (this.console == "true") {
+ node.log("\n"+util.inspect(msg, {colors:useColors, depth:10}));
+ }
+ if (this.active) {
+ sendDebug({id:this.id,name:this.name,topic:msg.topic,msg:msg,_path:msg._path});
+ }
+ } else { // debug just the msg.payload
+ if (this.console == "true") {
+ if (typeof msg.payload === "string") {
+ node.log((msg.payload.indexOf("\n") != -1 ? "\n" : "") + msg.payload);
+ }
+ else if (typeof msg.payload === "object") { node.log("\n"+util.inspect(msg.payload, {colors:useColors, depth:10})); }
+ else { node.log(util.inspect(msg.payload, {colors:useColors})); }
+ }
+ if (this.active) {
+ sendDebug({id:this.id,name:this.name,topic:msg.topic,msg:msg.payload,_path:msg._path});
+ }
+ }
+ });
+ }
+
+ RED.nodes.registerType("debug",DebugNode);
+
+ function sendDebug(msg) {
+ if (msg.msg instanceof Error) {
+ msg.msg = msg.msg.toString();
+ } else if (msg.msg instanceof Buffer) {
+ msg.msg = "(Buffer) "+msg.msg.toString('hex');
+ } else if (typeof msg.msg === 'object') {
+ var seen = [];
+ var ty = "(Object) ";
+ if (util.isArray(msg.msg)) { ty = "(Array) "; }
+ msg.msg = ty + 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";
+ } else if (msg.msg == null) {
+ msg.msg = "(undefined)";
+ }
+
+ if (msg.msg.length > debuglength) {
+ msg.msg = msg.msg.substr(0,debuglength) +" ....";
+ }
+
+ RED.comms.publish("debug",msg);
+ }
+
+ DebugNode.logHandler = new events.EventEmitter();
+ DebugNode.logHandler.on("log",function(msg) {
+ if (msg.level == "warn" || msg.level == "error") {
+ sendDebug(msg);
+ }
+ });
+ RED.log.addHandler(DebugNode.logHandler);
+
+ RED.httpAdmin.post("/debug/:id/:state", function(req,res) {
+ var node = RED.nodes.getNode(req.params.id);
+ var state = req.params.state;
+ if (node != null) {
+ if (state === "enable") {
+ node.active = true;
+ res.send(200);
+ } else if (state === "disable") {
+ node.active = false;
+ res.send(201);
+ } else {
+ res.send(404);
+ }
+ } else {
+ res.send(404);
+ }
+ });
+}
diff --git a/dgbuilder/core_nodes/core/75-exec.html b/dgbuilder/core_nodes/core/75-exec.html
new file mode 100644
index 00000000..567a34c1
--- /dev/null
+++ b/dgbuilder/core_nodes/core/75-exec.html
@@ -0,0 +1,68 @@
+<!--
+ 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="exec">
+ <div class="form-row">
+ <label for="node-input-command"><i class="fa fa-file"></i> Command</label>
+ <input type="text" id="node-input-command" placeholder="command">
+ </div>
+ <div class="form-row">
+ <label for="node-input-append"><i class="fa fa-list"></i> Append</label>
+ <input type="text" id="node-input-append" placeholder="extra input">
+ </div>
+ <div class="form-row">
+ <label>&nbsp;</label>
+ <input type="checkbox" id="node-input-useSpawn" placeholder="spawn" style="display: inline-block; width: auto; vertical-align: top;">
+ <label for="node-input-useSpawn" style="width: 70%;">Use spawn() instead of exec() ?</label>
+ </div>
+ <div class="form-row">
+ <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
+ <input type="text" id="node-input-name" placeholder="Name">
+ </div>
+ <div class="form-tips">Tip: <i>spawn</i> expects only one command word - and appended args to be comma separated.</div>
+</script>
+
+<script type="text/x-red" data-help-name="exec">
+ <p>Calls out to a system command.<br/></p>
+ <p>Provides 3 outputs... stdout, stderr, and return code.</p>
+ <p>By default uses exec() which calls the command, blocks while waiting for completion, and then returns the complete result in one go, along with any errors.</p>
+ <p>Optionally can use spawn() instead, which returns output from stdout and stderr as the command runs (ie one line at a time). On completion it then returns a return code (on the 3rd output).</p>
+ <p>Spawn only expect one command word, with all extra parameters to be comma separated and passed as the append.</p>
+ <p>The optional append gets added to the command after the <b>msg.payload</b> - so you can do things like pipe the result to another command.</p>
+</script>
+
+<script type="text/javascript">
+ RED.nodes.registerType('exec',{
+ category: 'advanced-function',
+ color:"darksalmon",
+ defaults: {
+ command: {value:"",required:true},
+ append: {value:""},
+ useSpawn: {value:""},
+ name: {value:""}
+ },
+ inputs:1,
+ outputs:3,
+ icon: "arrow-in.png",
+ align: "right",
+ label: function() {
+ return this.name||this.command;
+ },
+ labelStyle: function() {
+ return this.name?"node_label_italic":"";
+ }
+ });
+</script>
diff --git a/dgbuilder/core_nodes/core/75-exec.js b/dgbuilder/core_nodes/core/75-exec.js
new file mode 100644
index 00000000..a07b1401
--- /dev/null
+++ b/dgbuilder/core_nodes/core/75-exec.js
@@ -0,0 +1,84 @@
+/**
+ * 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.
+ **/
+
+module.exports = function(RED) {
+ "use strict";
+ var spawn = require('child_process').spawn;
+ var exec = require('child_process').exec;
+
+ function ExecNode(n) {
+ RED.nodes.createNode(this,n);
+ this.cmd = n.command.trim();
+ this.append = n.append.trim() || "";
+ this.useSpawn = n.useSpawn;
+
+ var node = this;
+ this.on("input", function(msg) {
+ node.status({fill:"blue",shape:"dot"});
+ if (this.useSpawn === true) {
+ // make the extra args into an array
+ // then prepend with the msg.payload
+ if (typeof(msg.payload !== "string")) { msg.payload = msg.payload.toString(); }
+ var arg = [];
+ if (node.append.length > 0) { arg = node.append.split(","); }
+ if (msg.payload.trim() !== "") { arg.unshift(msg.payload); }
+ node.log(node.cmd+" ["+arg+"]");
+ if (node.cmd.indexOf(" ") == -1) {
+ var ex = spawn(node.cmd,arg);
+ ex.stdout.on('data', function (data) {
+ //console.log('[exec] stdout: ' + data);
+ msg.payload = data.toString();
+ node.send([msg,null,null]);
+ });
+ ex.stderr.on('data', function (data) {
+ //console.log('[exec] stderr: ' + data);
+ msg.payload = data.toString();
+ node.send([null,msg,null]);
+ });
+ ex.on('close', function (code) {
+ //console.log('[exec] result: ' + code);
+ msg.payload = code;
+ node.status({});
+ node.send([null,null,msg]);
+ });
+ ex.on('error', function (code) {
+ node.warn(code);
+ });
+ }
+ else { node.error("Spawn command must be just the command - no spaces or extra parameters"); }
+ }
+ else {
+ var cl = node.cmd+" "+msg.payload+" "+node.append;
+ node.log(cl);
+ var child = exec(cl, function (error, stdout, stderr) {
+ msg.payload = stdout;
+ var msg2 = {payload:stderr};
+ var msg3 = null;
+ //console.log('[exec] stdout: ' + stdout);
+ //console.log('[exec] stderr: ' + stderr);
+ if (error !== null) {
+ msg3 = {payload:error};
+ //console.log('[exec] error: ' + error);
+ }
+ node.status({});
+ node.send([msg,msg2,msg3]);
+ });
+ }
+ });
+ }
+
+ RED.nodes.registerType("exec",ExecNode);
+}
diff --git a/dgbuilder/core_nodes/core/80-function.html b/dgbuilder/core_nodes/core/80-function.html
new file mode 100644
index 00000000..442c391d
--- /dev/null
+++ b/dgbuilder/core_nodes/core/80-function.html
@@ -0,0 +1,110 @@
+<!--
+ 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="function">
+ <div class="form-row">
+ <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
+ <input type="text" id="node-input-name" placeholder="Name">
+ </div>
+ <div class="form-row">
+ <label for="node-input-func"><i class="fa fa-wrench"></i> Function</label>
+ <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">
+ <label for="node-input-outputs"><i class="fa fa-random"></i> Outputs</label>
+ <input id="node-input-outputs" style="width: 60px; height: 1.7em;" value="1">
+ </div>
+ <div class="form-tips">See the Info tab for help writing functions.</div>
+</script>
+
+<script type="text/x-red" data-help-name="function">
+ <p>A function block where you can write code to do more interesting things.</p>
+ <p>The message is passed in as a JavaScript object called <code>msg</code>.</p>
+ <p>By convention it will have a <code>msg.payload</code> property containing
+ the body of the message.</p>
+ <p>The function should return the messages it wants to pass on to the next nodes
+ in the flow. It can return:</p>
+ <ul>
+ <li>a single message object - passed to nodes connected to the first output</li>
+ <li>an array of message objects - passed to nodes connected to the corresponding outputs</li>
+ </ul>
+ <p>If any element of the array is itself an array of messages, multiple
+ messages are sent to the corresponding output.</p>
+ <p>If null is returned, either by itself or as an element of the array, no
+ message is passed on.</p>
+ <p>See the <a target="_new" href="http://nodered.org/docs/writing-functions.html">online documentation</a> for more help.</p>
+
+</script>
+
+<script type="text/javascript">
+ RED.nodes.registerType('function',{
+ color:"#fdd0a2",
+ category: 'function',
+ defaults: {
+ name: {value:""},
+ func: {value:"\nreturn msg;"},
+ outputs: {value:1}
+ },
+ inputs:1,
+ outputs:1,
+ icon: "function.png",
+ label: function() {
+ return this.name;
+ },
+ oneditprepare: function() {
+ $( "#node-input-outputs" ).spinner({
+ min:1
+ });
+
+ function functionDialogResize(ev,ui) {
+ $("#node-input-func-editor").css("height",(ui.size.height-275)+"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-func-editor'),
+ lang:"js",
+ contents: $("#node-input-func").val()
+ });
+ RED.library.create({
+ url:"functions", // where to get the data from
+ type:"function", // the type of object the library is for
+ editor:that.editor, // the field name the main text body goes to
+ fields:['name','outputs']
+ });
+ $("#node-input-name").focus();
+
+ });
+ },
+ oneditsave: function() {
+ $("#node-input-func").val(this.editor.getText())
+ delete this.editor;
+ }
+ });
+</script>
diff --git a/dgbuilder/core_nodes/core/80-function.js b/dgbuilder/core_nodes/core/80-function.js
new file mode 100644
index 00000000..e1413a7a
--- /dev/null
+++ b/dgbuilder/core_nodes/core/80-function.js
@@ -0,0 +1,79 @@
+/**
+ * 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.
+ **/
+
+module.exports = function(RED) {
+ "use strict";
+ var util = require("util");
+ var vm = require("vm");
+
+ function FunctionNode(n) {
+ RED.nodes.createNode(this,n);
+ this.name = n.name;
+ this.func = n.func;
+ var functionText = "var results = null; results = (function(msg){"+this.func+"\n})(msg);";
+ this.topic = n.topic;
+ var sandbox = {
+ console:console,
+ util:util,
+ Buffer:Buffer,
+ context: {
+ global:RED.settings.functionGlobalContext || {}
+ }
+ };
+ var context = vm.createContext(sandbox);
+ try {
+ this.script = vm.createScript(functionText);
+ this.on("input", function(msg) {
+ try {
+ var start = process.hrtime();
+ context.msg = msg;
+ this.script.runInContext(context);
+ var results = context.results;
+ if (results == null) {
+ results = [];
+ } else if (results.length == null) {
+ results = [results];
+ }
+ if (msg._topic) {
+ for (var m in results) {
+ if (results[m]) {
+ if (util.isArray(results[m])) {
+ for (var n=0; n < results[m].length; n++) {
+ results[m][n]._topic = msg._topic;
+ }
+ } else {
+ results[m]._topic = msg._topic;
+ }
+ }
+ }
+ }
+ this.send(results);
+ var duration = process.hrtime(start);
+ if (process.env.NODE_RED_FUNCTION_TIME) {
+ this.status({fill:"yellow",shape:"dot",text:""+Math.floor((duration[0]* 1e9 + duration[1])/10000)/100});
+ }
+ } catch(err) {
+ this.error(err.toString());
+ }
+ });
+ } catch(err) {
+ this.error(err);
+ }
+ }
+
+ RED.nodes.registerType("function",FunctionNode);
+ RED.library.register("functions");
+}
diff --git a/dgbuilder/core_nodes/core/80-template.html b/dgbuilder/core_nodes/core/80-template.html
new file mode 100644
index 00000000..dc014d37
--- /dev/null
+++ b/dgbuilder/core_nodes/core/80-template.html
@@ -0,0 +1,102 @@
+<!--
+ Copyright 2013,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="template">
+ <div class="form-row">
+ <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
+ <input type="text" id="node-input-name" placeholder="Name">
+ </div>
+ <div class="form-row">
+ <label for="node-input-template"><i class="fa fa-file-code-o"></i> Template</label>
+ <input type="hidden" id="node-input-template" autofocus="autofocus">
+ <div style="height: 250px;" class="node-text-editor" id="node-input-template-editor" ></div>
+ </div>
+ <div class="form-row">
+ <label for="node-input-field"><i class="fa fa-edit"></i> Property</label>
+ msg.<input type="text" id="node-input-field" placeholder="payload" style="width: 64%;">
+ </div>
+</script>
+
+<script type="text/x-red" data-help-name="template">
+ <p>Creates a new message based on the provided template.</p>
+ <p>This uses the <i><a href="http://mustache.github.io/mustache.5.html" target="_new">mustache</a></i> format.</p>
+ <p>For example, when a template of:
+ <pre>Hello {{name}}. Today is {{date}}</pre>
+ <p>receives a message containing:
+ <pre>{
+ name: "Fred",
+ date: "Monday"
+ payload: ...
+}</pre>
+ <p>The resulting payload will be:
+ <pre>Hello Fred. Today is Monday</pre>
+</script>
+
+<script type="text/javascript">
+ RED.nodes.registerType('template',{
+ color:"rgb(243, 181, 103)",
+ category: 'function',
+ defaults: {
+ name: {value:""},
+ field: {value:"payload"},
+ template: {value:"This is the payload: {{payload}}!"},
+ },
+ inputs:1,
+ outputs:1,
+ icon: "template.png",
+ label: function() {
+ return this.name;
+ },
+ oneditprepare: function() {
+
+ function templateDialogResize(ev,ui) {
+ $("#node-input-template-editor").css("height",(ui.size.height-200)+"px");
+ };
+
+ $( "#dialog" ).on("dialogresize", templateDialogResize);
+ $( "#dialog" ).one("dialogopen", function(ev) {
+ var size = $( "#dialog" ).dialog('option','sizeCache-template');
+ if (size) {
+ templateDialogResize(null,{size:size});
+ }
+ });
+ $( "#dialog" ).one("dialogclose", function(ev,ui) {
+ var height = $( "#dialog" ).dialog('option','height');
+ $( "#dialog" ).off("dialogresize",templateDialogResize);
+ });
+
+ var that = this;
+ require(["orion/editor/edit"], function(edit) {
+ that.editor = edit({
+ parent:document.getElementById('node-input-template-editor'),
+ lang:"html",
+ contents: $("#node-input-template").val()
+ });
+ RED.library.create({
+ url:"templates", // where to get the data from
+ type:"template", // the type of object the library is for
+ editor:that.editor, // the field name the main text body goes to
+ fields:['name','field']
+ });
+ $("#node-input-name").focus();
+ });
+ },
+ oneditsave: function() {
+ $("#node-input-template").val(this.editor.getText())
+ delete this.editor;
+ }
+ });
+</script>
diff --git a/dgbuilder/core_nodes/core/80-template.js b/dgbuilder/core_nodes/core/80-template.js
new file mode 100644
index 00000000..7c84142d
--- /dev/null
+++ b/dgbuilder/core_nodes/core/80-template.js
@@ -0,0 +1,61 @@
+/**
+ * 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.
+ **/
+
+module.exports = function(RED) {
+ "use strict";
+ var mustache = require("mustache");
+
+ function TemplateNode(n) {
+ RED.nodes.createNode(this,n);
+ this.name = n.name;
+ this.field = n.field || "payload";
+ this.template = n.template;
+ var node = this;
+
+ var b = node.field.split(".");
+ var i = 0;
+ var m = null;
+ var rec = function(obj) {
+ i += 1;
+ if ((i < b.length) && (typeof obj[b[i-1]] === "object")) {
+ rec(obj[b[i-1]]); // not there yet - carry on digging
+ }
+ else {
+ if (i === b.length) { // we've finished so assign the value
+ obj[b[i-1]] = mustache.render(node.template,m);
+ node.send(m);
+ }
+ else {
+ obj[b[i-1]] = {}; // needs to be a new object so create it
+ rec(obj[b[i-1]]); // and carry on digging
+ }
+ }
+ }
+
+ node.on("input", function(msg) {
+ try {
+ m = msg;
+ i = 0;
+ rec(msg);
+ } catch(err) {
+ node.error(err.message);
+ }
+ });
+ }
+
+ RED.nodes.registerType("template",TemplateNode);
+ RED.library.register("templates");
+}
diff --git a/dgbuilder/core_nodes/core/89-delay.html b/dgbuilder/core_nodes/core/89-delay.html
new file mode 100644
index 00000000..dcb0a5b9
--- /dev/null
+++ b/dgbuilder/core_nodes/core/89-delay.html
@@ -0,0 +1,167 @@
+<!--
+ 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.
+-->
+
+<!-- First, the content of the edit dialog is defined. -->
+<script type="text/x-red" data-template-name="delay">
+
+ <div class="form-row">
+ <label for="node-input-pauseType"><i class="fa fa-tasks"></i> Action</label>
+ <select id="node-input-pauseType" style="width:270px !important">
+ <option value="delay">Delay message</option>
+ <option value="rate">Limit rate to</option>
+ <option value="random">Random delay</option>
+ </select>
+ </div>
+ <div id="delay-details" class="form-row">
+ <label for="node-input-timeout"><i class="fa fa-clock-o"></i> For</label>
+ <input type="text" id="node-input-timeout" placeholder="Time" style="direction:rtl; width:50px !important">
+ <select id="node-input-timeoutUnits" style="width:200px !important">
+ <option value="milliseconds">Milliseconds</option>
+ <option value="seconds">Seconds</option>
+ <option value="minutes">Minutes</option>
+ <option value="hours">Hours</option>
+ <option value="days">Days</option>
+ </select>
+ </div>
+
+ <div id="rate-details" class="form-row">
+ <label for="node-input-rate"><i class="fa fa-clock-o"></i> To</label>
+ <input type="text" id="node-input-rate" placeholder="1" style="direction:rtl; width:30px !important">
+ <label for="node-input-reateUnits">msg(s) per</label>
+ <select id="node-input-rateUnits" style="width:140px !important">
+ <option value="second">Second</option>
+ <option value="minute">Minute</option>
+ <option value="hour">Hour</option>
+ <option value="day">Day</option>
+ </select>
+ <br/>
+ <input style="margin: 20px 0 20px 100px; width: 30px;" type="checkbox" id="node-input-drop"><label style="width: 250px;" for="node-input-drop">drop intermediate messages</label>
+ </div>
+
+ <div id="random-details" class="form-row">
+ <label for="node-input-randomFirst"><i class="fa fa-clock-o"></i> Between</label>
+ <input type="text" id="node-input-randomFirst" placeholder="" style="directon:rtl; width:30px !important">
+ <label for="node-input-randomLast" style="width:20px"> &amp; </label>
+ <input type="text" id="node-input-randomLast" placeholder="" style="directon:rtl; width:30px !important">
+ <select id="node-input-randomUnits" style="width:140px !important">
+ <option value="milliseconds">Milliseconds</option>
+ <option value="seconds">Seconds</option>
+ <option value="minutes">Minutes</option>
+ <option value="hours">Hours</option>
+ <option value="days">Days</option>
+ </select>
+ </div>
+
+ <div class="form-row">
+ <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
+ <input type="text" id="node-input-name" placeholder="Name">
+ </div>
+
+</script>
+
+<!-- Next, some simple help text is provided for the node. -->
+<script type="text/x-red" data-help-name="delay">
+ <p>Introduces a delay into a flow or rate limts messges</p>
+ <p>Default delay is 5 seconds and rate limit of 1 msg/second, but both can be configured</p>
+</script>
+
+<!-- Finally, the node type is registered along with all of its properties -->
+<script type="text/javascript">
+ RED.nodes.registerType('delay',{
+ category: 'function', // the palette category
+ color:"#E6E0F8",
+ defaults: { // defines the editable properties of the node
+ name: {value:""}, // along with default values.
+ pauseType: {value:"delay", required:true},
+ timeout: {value:"5", required:true, validate:RED.validators.number()},
+ timeoutUnits: {value:"seconds"},
+ rate: {value:"1", required:true, validate:RED.validators.number()},
+ rateUnits: {value: "second"},
+ randomFirst: {value:"1", required:true, validate:RED.validators.number()},
+ randomLast: {value:"5", required:true, validate:RED.validators.number()},
+ randomUnits: {value: "seconds"},
+ drop: {value:false}
+ },
+ inputs:1, // set the number of inputs - only 0 or 1
+ outputs:1, // set the number of outputs - 0 to n
+ icon: "timer.png", // set the icon (held in public/icons)
+ label: function() { // sets the default label contents
+ if (this.pauseType == "delay") {
+ var units = this.timeoutUnits ? this.timeoutUnits.charAt(0) : "s";
+ if (this.timeoutUnits == "milliseconds") { units = "ms"; }
+ return this.name||"delay "+this.timeout+" " + units;
+ } else if (this.pauseType == "rate") {
+ var units = this.rateUnits ? this.rateUnits.charAt(0) : "s";
+ return this.name||"limit "+this.rate+" msg/"+ units;
+ } else if (this.pauseType == "random") {
+ return this.name || "random";
+ }
+ return "foo";
+ },
+ labelStyle: function() { // sets the class to apply to the label
+ return this.name?"node_label_italic":"";
+ },
+ oneditprepare: function() {
+ $( "#node-input-timeout" ).spinner({min:1,max:60});
+ $( "#node-input-rate" ).spinner({min:1});
+
+ $( "#node-input-randomFirst" ).spinner({min:0});
+ $( "#node-input-randomLast" ).spinner({min:1});
+
+ if (this.pauseType == "delay") {
+ $("#delay-details").show();
+ $("#rate-details").hide();
+ $("#random-details").hide();
+ } else if (this.pauseType == "rate") {
+ $("#delay-details").hide();
+ $("#rate-details").show();
+ $("#random-details").hide();
+ } else if (this.pauseType == "random") {
+ $("#delay-details").hide();
+ $("#rate-details").hide();
+ $("#random-details").show();
+ }
+
+ if (!this.timeoutUnits) {
+ $("#node-input-timeoutUnits option").filter(function() {
+ return $(this).val() == 'seconds';
+ }).attr('selected', true);
+ }
+
+ if (!this.randomUnits) {
+ $("#node-input-randomUnits option").filter(function() {
+ return $(this).val() == 'seconds';
+ }).attr('selected', true);
+ }
+
+ $("#node-input-pauseType").on("change",function() {
+ if (this.value == "delay") {
+ $("#delay-details").show();
+ $("#rate-details").hide();
+ $("#random-details").hide();
+ } else if (this.value == "rate") {
+ $("#delay-details").hide();
+ $("#rate-details").show();
+ $("#random-details").hide();
+ } else if (this.value == "random") {
+ $("#delay-details").hide();
+ $("#rate-details").hide();
+ $("#random-details").show();
+ }
+ });
+ }
+ });
+</script>
diff --git a/dgbuilder/core_nodes/core/89-delay.js b/dgbuilder/core_nodes/core/89-delay.js
new file mode 100644
index 00000000..3c4e1c01
--- /dev/null
+++ b/dgbuilder/core_nodes/core/89-delay.js
@@ -0,0 +1,171 @@
+/**
+ * Copyright 2013, 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.
+ **/
+
+//Simple node to introduce a pause into a flow
+module.exports = function(RED) {
+ "use strict";
+
+ var MILLIS_TO_NANOS = 1000000;
+ var SECONDS_TO_NANOS = 1000000000;
+
+ function random(n) {
+ var wait = n.randomFirst + (n.diff * Math.random());
+ if (n.buffer.length > 0) {
+ n.send(n.buffer.pop());
+ n.randomID = setTimeout(function() {random(n);},wait);
+ } else {
+ n.randomID = -1;
+ }
+ }
+
+ function DelayNode(n) {
+ RED.nodes.createNode(this,n);
+
+ this.pauseType = n.pauseType;
+ this.timeoutUnits = n.timeoutUnits;
+ this.randomUnits = n.randomUnits;
+ this.rateUnits = n.rateUnits;
+
+ if (n.timeoutUnits === "milliseconds") {
+ this.timeout = n.timeout;
+ } else if (n.timeoutUnits === "seconds") {
+ this.timeout = n.timeout * 1000;
+ } else if (n.timeoutUnits === "minutes") {
+ this.timeout = n.timeout * (60 * 1000);
+ } else if (n.timeoutUnits === "hours") {
+ this.timeout = n.timeout * (60 * 60 * 1000);
+ } else if (n.timeoutUnits === "days") {
+ this.timeout = n.timeout * (24 * 60 * 60 * 1000);
+ }
+
+ if (n.rateUnits === "second") {
+ this.rate = 1000/n.rate;
+ } else if (n.rateUnits === "minute") {
+ this.rate = (60 * 1000)/n.rate;
+ } else if (n.rateUnits === "hour") {
+ this.rate = (60 * 60 * 1000)/n.rate;
+ } else if (n.rateUnits === "day") {
+ this.rate = (24 * 60 * 60 * 1000)/n.rate;
+ }
+
+ if (n.randomUnits === "milliseconds") {
+ this.randomFirst = n.randomFirst;
+ this.randomLast = n.randomLast;
+ } else if (n.randomUnits === "seconds") {
+ this.randomFirst = n.randomFirst * 1000;
+ this.randomLast = n.randomLast * 1000;
+ } else if (n.randomUnits === "minutes") {
+ this.randomFirst = n.randomFirst * (60 * 1000);
+ this.randomLast = n.randomLast * (60 * 1000);
+ } else if (n.randomUnits === "hours") {
+ this.randomFirst = n.randomFirst * (60 * 60 * 1000);
+ this.randomLast = n.randomLast * (60 * 60 * 1000);
+ } else if (n.randomUnits === "days") {
+ this.randomFirst = n.randomFirst * (24 * 60 * 60 * 1000);
+ this.randomLast = n.randomLast * (24 * 60 * 60 * 1000);
+ }
+
+ this.diff = this.randomLast - this.randomFirst;
+ this.name = n.name;
+ this.idList = [];
+ this.buffer = [];
+ this.intervalID = -1;
+ this.randomID = -1;
+ this.lastSent;
+ this.drop = n.drop;
+ var node = this;
+
+ if (this.pauseType === "delay") {
+ this.on("input", function(msg) {
+ var id;
+ id = setTimeout(function(){
+ node.idList.splice(node.idList.indexOf(id),1);
+ node.send(msg);
+ }, node.timeout);
+ this.idList.push(id);
+ });
+
+ this.on("close", function() {
+ for (var i=0; i<this.idList.length; i++ ) {
+ clearTimeout(this.idList[i]);
+ }
+ this.idList = [];
+ });
+
+ } else if (this.pauseType === "rate") {
+ this.on("input", function(msg) {
+ if (!node.drop) {
+ if ( node.intervalID !== -1) {
+ node.buffer.push(msg);
+ if (node.buffer.length > 0) {
+ node.status({text:node.buffer.length});
+ }
+ if (node.buffer.length > 1000) {
+ node.warn(this.name + " buffer exceeded 1000 messages");
+ }
+ } else {
+ node.send(msg);
+ node.intervalID = setInterval(function() {
+ if (node.buffer.length === 0) {
+ clearInterval(node.intervalID);
+ node.intervalID = -1;
+ node.status({text:""});
+ }
+
+ if (node.buffer.length > 0) {
+ node.send(node.buffer.shift());
+ node.status({text:node.buffer.length});
+ }
+ },node.rate);
+ }
+ } else {
+ var timeSinceLast;
+ if (node.lastSent) {
+ timeSinceLast = process.hrtime(node.lastSent);
+ }
+ if (!node.lastSent) { // ensuring that we always send the first message
+ node.lastSent = process.hrtime();
+ node.send(msg);
+ } else if ( ( (timeSinceLast[0] * SECONDS_TO_NANOS) + timeSinceLast[1] ) > (node.rate * MILLIS_TO_NANOS) ) {
+ node.lastSent = process.hrtime();
+ node.send(msg);
+ }
+ }
+ });
+
+ this.on("close", function() {
+ clearInterval(this.intervalID);
+ this.buffer = [];
+ });
+
+ } else if (this.pauseType === "random") {
+ this.on("input",function(msg){
+ node.buffer.push(msg);
+ if (node.randomID === -1) {
+ var wait = node.randomFirst + (node.diff * Math.random());
+ node.randomID = setTimeout(function() {random(node);},wait);
+ }
+ });
+
+ this.on("close", function (){
+ if (this.randomID !== -1) {
+ clearTimeout(this.randomID);
+ }
+ });
+ }
+ }
+ RED.nodes.registerType("delay",DelayNode);
+}
diff --git a/dgbuilder/core_nodes/core/89-trigger.html b/dgbuilder/core_nodes/core/89-trigger.html
new file mode 100644
index 00000000..f3ec530d
--- /dev/null
+++ b/dgbuilder/core_nodes/core/89-trigger.html
@@ -0,0 +1,130 @@
+<!--
+ 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="trigger">
+ <div class="form-row">
+ <label for="node-input-op1type"><i class="fa fa-arrow-up"></i> Output</label>
+ <select id="node-input-op1type" style="width:73% !important">
+ <option value="val">the value below</option>
+ <option value="pay">the existing payload</option>
+ <option value="nul">nothing (no output)</option>
+ </select>
+ </div>
+ <div class="form-row" id="node-op1">
+ <label for="node-input-op1">&nbsp;</label>
+ <input type="text" id="node-input-op1">
+ </div>
+ <div class="form-row">
+ <label for="node-input-duration"><i class="fa fa-clock-o"></i> then wait</label>
+ <input type="text" id="node-input-duration" placeholder="250" style="direction:rtl; width:70px !important">
+ <select id="node-input-units" style="width:140px !important">
+ <option value="ms">Milliseconds</option>
+ <option value="s">Seconds</option>
+ <option value="min">Minutes</option>
+ <option value="hr">Hours</option>
+ </select>
+ </div>
+ <div class="form-row">
+ <label for="node-input-op2type"><i class="fa fa-arrow-down"></i> output</label>
+ <select id="node-input-op2type" style="width:73% !important">
+ <option value="val">the value below</option>
+ <option value="pay">the existing payload</option>
+ <option value="nul">nothing (no output)</option>
+ </select>
+ </div>
+ <div class="form-row" id="node-op2">
+ <label for="node-input-op2">&nbsp;</label>
+ <input type="text" id="node-input-op2">
+ </div>
+ <div class="form-row">
+ <label for="node-input-extend"><i class="fa fa-repeat"></i> and</label>
+ <select id="node-input-extend" style="width:73% !important">
+ <option value="false">don't extend the timer if retriggered</option>
+ <option value="true">extend the timer if retriggered</option>
+ </select>
+ </div>
+ <div class="form-row">
+ <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
+ <input type="text" id="node-input-name" placeholder="Name">
+ </div>
+ <!-- <div class="form-tips">Tip: Outputs can be values, null, {{templated}} or msg.payload<br/> -->
+ <div class="form-tips">Setting the timeout to 0 sets an infinite timeout = single shot.</div>
+ <script>
+ {
+ $("#node-input-op1type").change(function() {
+ if ($("#node-input-op1type").val() == "val") { $("#node-op1").show(); }
+ else { $("#node-op1").hide(); }
+ });
+ $("#node-input-op2type").change(function() {
+ if ($("#node-input-op2type").val() == "val") { $("#node-op2").show(); }
+ else { $("#node-op2").hide(); }
+ });
+ }
+ </script>
+</script>
+
+<script type="text/x-red" data-help-name="trigger">
+ <p>Creates two messages on the output separated by a timeout whenever ANY <b>msg</b> arrives on the input.</p>
+ <p>For example, this can be used to toggle a Raspberry PI GPIO pin on and off.</p>
+ <p>The two output states can be specified as can the duration of the timer.
+ Either output can be set to a value, or templated from the inbound
+ <b>msg</b> using mustache syntax. <pre>The payload is {{payload}}</pre></p>
+ <p>If the payload is an object then setting the output to <i>existing payload</i> will pass the complete payload object through.</p>
+ <p>Optionally the timer can be extended by being retriggered... or not.</p>
+ <p>By setting the first output to <i>nothing</i>, and selecting extend timer - a watchdog timer can be created.
+ No output will happen as long as repeated inputs occur within the timeout period.</p>
+ <p>Setting the timer to 0 creates an "infinite" timeout - the first output will happen but the second
+ never will, and neither can the first be retriggered - so a true one shot.</p>
+ <p>If a <b>msg.reset</b> property is present any timeout currently in progress
+ will be cleared and the second output will not happen.</p>
+</script>
+
+<script type="text/javascript">
+ RED.nodes.registerType('trigger',{
+ category: 'function',
+ color:"#E6E0F8",
+ defaults: {
+ op1: {value:"1"},
+ op2: {value:"0"},
+ op1type: {value:""},
+ op2type: {value:""},
+ duration: {value:"250",required:true,validate:RED.validators.number()},
+ extend: {value:"false"},
+ units: {value: "ms"},
+ name: {value:""}
+ },
+ inputs:1,
+ outputs:1,
+ icon: "trigger.png",
+ label: function() {
+ if (this.duration > 0) {
+ return this.name||"trigger "+this.duration+this.units;
+ }
+ else {
+ return this.name||"trigger once &infin;";
+ }
+ },
+ labelStyle: function() {
+ return this.name?"node_label_italic":"";
+ },
+ oneditprepare: function() {
+ $( "#node-input-duration" ).spinner({
+ min:1,
+ increment:25
+ });
+ }
+ });
+</script>
diff --git a/dgbuilder/core_nodes/core/89-trigger.js b/dgbuilder/core_nodes/core/89-trigger.js
new file mode 100644
index 00000000..d86a2130
--- /dev/null
+++ b/dgbuilder/core_nodes/core/89-trigger.js
@@ -0,0 +1,91 @@
+/**
+ * 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.
+ **/
+
+module.exports = function(RED) {
+ "use strict";
+ var mustache = require("mustache");
+ function TriggerNode(n) {
+ RED.nodes.createNode(this,n);
+ this.op1 = n.op1 || "1";
+ this.op2 = n.op2 || "0";
+ this.op1type = n.op1type || "val";
+ this.op2type = n.op2type || "val";
+ this.extend = n.extend || false;
+ this.units = n.units || "ms";
+ this.duration = n.duration || 250;
+ if (this.duration <= 0) { this.duration = 0; }
+ else {
+ if (this.units == "s") { this.duration = this.duration * 1000; }
+ if (this.units == "min") { this.duration = this.duration * 1000 * 60; }
+ if (this.units == "hr") { this.duration = this.duration * 1000 *60 * 60; }
+ }
+ this.op1Templated = this.op1.indexOf("{{") != -1;
+ this.op2Templated = this.op2.indexOf("{{") != -1;
+ if (!isNaN(this.op1)) { this.op1 = Number(this.op1); }
+ if (!isNaN(this.op2)) { this.op2 = Number(this.op2); }
+ if (this.op1 == "true") { this.op1 = true; }
+ if (this.op2 == "true") { this.op1 = true; }
+ if (this.op1 == "false") { this.op2 = false; }
+ if (this.op2 == "false") { this.op2 = false; }
+ if (this.op1 == "null") { this.op1 = null; }
+ if (this.op2 == "null") { this.op1 = null; }
+ try { this.op1 = JSON.parse(this.op1); }
+ catch(e) { this.op1 = this.op1; }
+ try { this.op2 = JSON.parse(this.op2); }
+ catch(e) { this.op2 = this.op2; }
+
+ var node = this;
+ var tout = null;
+ var m2;
+ this.on("input", function(msg) {
+ if (msg.hasOwnProperty("reset")) {
+ clearTimeout(tout);
+ tout = null;
+ }
+ else {
+ if (!tout) {
+ if (node.op2type === "pay") { m2 = msg.payload; }
+ else if (node.op2Templated) { m2 = mustache.render(node.op2,msg); }
+ else { m2 = node.op2; }
+ if (node.op1type === "pay") { }
+ else if (node.op1Templated) { msg.payload = mustache.render(node.op1,msg); }
+ else { msg.payload = node.op1; }
+ if (node.op1type !== "nul") { node.send(msg); }
+ if (node.duration === 0) { tout = "infinite"; }
+ else {
+ tout = setTimeout(function() {
+ msg.payload = m2;
+ if (node.op2type !== "nul") { node.send(msg); }
+ tout = null;
+ },node.duration);
+ }
+ }
+ else if ((node.extend == "true") && (node.duration > 0)) {
+ clearTimeout(tout);
+ tout = setTimeout(function() {
+ msg.payload = m2;
+ if (node.op2type !== "nul") { node.send(msg); }
+ tout = null;
+ },node.duration);
+ }
+ }
+ });
+ this.on("close", function() {
+ if (tout) { clearTimeout(tout); }
+ });
+ }
+ RED.nodes.registerType("trigger",TriggerNode);
+}
diff --git a/dgbuilder/core_nodes/core/90-comment.html b/dgbuilder/core_nodes/core/90-comment.html
new file mode 100644
index 00000000..3638fdaa
--- /dev/null
+++ b/dgbuilder/core_nodes/core/90-comment.html
@@ -0,0 +1,86 @@
+<!--
+ 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="comment">
+ <div class="form-row">
+ <label for="node-input-name"><i class="fa fa-comment"></i> Comment</label>
+ <input type="text" id="node-input-name" placeholder="Comment">
+ </div>
+ <div class="form-row">
+ <label for="node-input-info" style="width: 100% !important;"><i class="fa fa-comments"></i> More</label>
+ <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>
+</script>
+
+<script type="text/x-red" data-help-name="comment">
+ <p>Simple comment block.</p>
+</script>
+
+<script type="text/javascript">
+ RED.nodes.registerType('comment',{
+ category: 'function',
+ color:"#ffffff",
+ defaults: {
+ name: {value:""},
+ info: {value:""}
+ },
+ inputs:0,
+ outputs:0,
+ icon: "comment.png",
+ label: function() {
+ return this.name||"";
+ },
+ 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()
+ });
+ $("#node-input-name").focus();
+ });
+ },
+ oneditsave: function() {
+ $("#node-input-info").val(this.editor.getText());
+ delete this.editor;
+ }
+ });
+</script>
diff --git a/dgbuilder/core_nodes/core/90-comment.js b/dgbuilder/core_nodes/core/90-comment.js
new file mode 100644
index 00000000..ef5f0800
--- /dev/null
+++ b/dgbuilder/core_nodes/core/90-comment.js
@@ -0,0 +1,23 @@
+/**
+ * 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.
+ **/
+
+module.exports = function(RED) {
+ "use strict";
+ function CommentNode(n) {
+ RED.nodes.createNode(this,n);
+ }
+ RED.nodes.registerType("comment",CommentNode);
+}
diff --git a/dgbuilder/core_nodes/core/98-unknown.html b/dgbuilder/core_nodes/core/98-unknown.html
new file mode 100644
index 00000000..19a4ad59
--- /dev/null
+++ b/dgbuilder/core_nodes/core/98-unknown.html
@@ -0,0 +1,49 @@
+<!--
+ 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="unknown">
+ <div class="form-tips"><p>This node is a type unknown to your installation of Node-RED.</p>
+ <p><i>If you deploy with the node in this state, it will lose all of its configuration.</i></p>
+ <p>See the Info side bar for more help</p></div>
+</script>
+
+<script type="text/x-red" data-help-name="unknown">
+ <p>This node is a type unknown to your installation of Node-RED.</p>
+ <p><i>If you deploy with the node in this state, it will lose all of its configuration.</i></p>
+ <p>It is possible this node type is already installed, but is missing a dependency. Check the Node-RED start-up log for
+ any error messages associated with the missing node type. Use <b>npm install &lt;module&gt;</b> to install any missing modules
+ and restart Node-RED and reimport the nodes.</p>
+ <p>Otherwise, you should contact the author of the flow to obtain a copy of the missing node type.</p>
+</script>
+
+<script type="text/javascript">
+ RED.nodes.registerType('unknown',{
+ category: 'unknown',
+ color:"#fff0f0",
+ defaults: {
+ name: {value:""}
+ },
+ inputs:1,
+ outputs:1,
+ icon: "",
+ label: function() {
+ return "("+this.name+")"||"unknown";
+ },
+ labelStyle: function() {
+ return "node_label_unknown";
+ }
+ });
+</script>
diff --git a/dgbuilder/core_nodes/core/98-unknown.js b/dgbuilder/core_nodes/core/98-unknown.js
new file mode 100644
index 00000000..ed4716b8
--- /dev/null
+++ b/dgbuilder/core_nodes/core/98-unknown.js
@@ -0,0 +1,23 @@
+/**
+ * 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.
+ **/
+
+module.exports = function(RED) {
+ "use strict";
+ function UnknownNode(n) {
+ RED.nodes.createNode(this,n);
+ }
+ RED.nodes.registerType("unknown",UnknownNode);
+}
diff --git a/dgbuilder/core_nodes/deprecated/61-imap.html b/dgbuilder/core_nodes/deprecated/61-imap.html
new file mode 100644
index 00000000..9702cd64
--- /dev/null
+++ b/dgbuilder/core_nodes/deprecated/61-imap.html
@@ -0,0 +1,56 @@
+<!--
+ 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="imap">
+ <div class="form-row node-input-repeat">
+ <label for="node-input-repeat"><i class="fa fa-repeat"></i>Repeat (S)</label>
+ <input type="text" id="node-input-repeat" placeholder="300">
+ </div>
+ <div class="form-row">
+ <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
+ <input type="text" id="node-input-name" placeholder="Name">
+ </div>
+</script>
+
+<script type="text/x-red" data-help-name="imap">
+ <p>Repeatedly gets a <b>single email</b> from an IMAP server and forwards on as a msg if not already seen.</p>
+ <p>The subject is loaded into <b>msg.topic</b> and <b>msg.payload</b> is the plain text body.
+ If there is text/html then that is returned in <b>msg.html</b>. <b>msg.from</b> and <b>msg.date</b> are also set if you need them.</p>
+ <p>Uses the imap module - you also need to pre-configure your email settings in a file emailkeys.js as per below.</p>
+ <p><pre>module.exports = { service: "Gmail", user: "blahblah@gmail.com", pass: "password", server: "imap.gmail.com", port: "993" }</pre></p>
+ <p>This <b>must</b> be located in the directory <b>above</b> node-red.</p>
+ <p><b>Note:</b> this node <i>only</i> gets the most recent single email from the inbox, so set the repeat (polling) time appropriately.</p>
+</script>
+
+<script type="text/javascript">
+ RED.nodes.registerType('imap',{
+ category: 'deprecated',
+ color:"#c7e9c0",
+ defaults: {
+ repeat: {value:"300",required:true},
+ name: {value:""}
+ },
+ inputs:0,
+ outputs:1,
+ icon: "envelope.png",
+ label: function() {
+ return this.name||"IMAP";
+ },
+ labelStyle: function() {
+ return (this.name||!this.topic)?"node_label_italic":"";
+ }
+ });
+</script>
diff --git a/dgbuilder/core_nodes/deprecated/61-imap.js b/dgbuilder/core_nodes/deprecated/61-imap.js
new file mode 100644
index 00000000..aa2b4beb
--- /dev/null
+++ b/dgbuilder/core_nodes/deprecated/61-imap.js
@@ -0,0 +1,139 @@
+/**
+ * Copyright 2013 IBM Corp.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ **/
+
+var RED = require(process.env.NODE_RED_HOME+"/red/red");
+var Imap = require('imap');
+var util = require('util');
+
+try {
+ var emailkey = RED.settings.email || require(process.env.NODE_RED_HOME+"/../emailkeys.js");
+} catch (err) {
+ //util.log("[61-imap.js] Info : No Email credentials found.");
+}
+
+if (emailkey) {
+ var imap = new Imap({
+ user: emailkey.user,
+ password: emailkey.pass,
+ host: emailkey.server||"imap.gmail.com",
+ port: emailkey.port||"993",
+ tls: true,
+ tlsOptions: { rejectUnauthorized: false }
+ });
+
+ function openInbox(cb) {
+ imap.openBox('INBOX', true, cb);
+ }
+}
+
+function ImapNode(n) {
+ RED.nodes.createNode(this,n);
+ this.warn("This node has been deprecated and will be deleted in a future release. Please update your flow to use the 'e-mail in' node.");
+ this.name = n.name;
+ this.repeat = n.repeat * 1000 || 300000;
+ var node = this;
+ this.interval_id = null;
+ var oldmail = {};
+
+ if (!isNaN(this.repeat) && this.repeat > 0) {
+ node.log("repeat = "+this.repeat);
+ this.interval_id = setInterval( function() {
+ node.emit("input",{});
+ }, this.repeat );
+ }
+
+ this.on("input", function(msg) {
+ if (imap) {
+ imap.once('ready', function() {
+ var pay = {};
+ openInbox(function(err, box) {
+ if (box.messages.total > 0) {
+ var f = imap.seq.fetch(box.messages.total + ':*', { bodies: ['HEADER.FIELDS (FROM SUBJECT DATE)','TEXT'] });
+ f.on('message', function(msg, seqno) {
+ node.log('message: #'+ seqno);
+ var prefix = '(#' + seqno + ') ';
+ msg.on('body', function(stream, info) {
+ var buffer = '';
+ stream.on('data', function(chunk) {
+ buffer += chunk.toString('utf8');
+ });
+ stream.on('end', function() {
+ if (info.which !== 'TEXT') {
+ pay.from = Imap.parseHeader(buffer).from[0];
+ pay.topic = Imap.parseHeader(buffer).subject[0];
+ pay.date = Imap.parseHeader(buffer).date[0];
+ } else {
+ var parts = buffer.split("Content-Type");
+ for (var p in parts) {
+ if (parts[p].indexOf("text/plain") >= 0) {
+ pay.payload = parts[p].split("\n").slice(1,-2).join("\n").trim();
+ }
+ if (parts[p].indexOf("text/html") >= 0) {
+ pay.html = parts[p].split("\n").slice(1,-2).join("\n").trim();
+ }
+ }
+ //pay.body = buffer;
+ }
+ });
+ });
+ msg.on('end', function() {
+ //node.log('Finished: '+prefix);
+ });
+ });
+ f.on('error', function(err) {
+ node.warn('fetch error: ' + err);
+ });
+ f.on('end', function() {
+ if (JSON.stringify(pay) !== oldmail) {
+ node.send(pay);
+ oldmail = JSON.stringify(pay);
+ node.log('sent new message: '+pay.topic);
+ }
+ else { node.log('duplicate not sent: '+pay.topic); }
+ imap.end();
+ });
+ }
+ else {
+ // node.log("you have achieved inbox zero");
+ imap.end();
+ }
+ });
+ });
+ imap.connect();
+ }
+ else { node.warn("No Email credentials found. See info panel."); }
+ });
+
+ if (imap) {
+ imap.on('error', function(err) {
+ util.log(err);
+ });
+ }
+
+ this.on("error", function(err) {
+ node.log("error: ",err);
+ });
+
+ this.on("close", function() {
+ if (this.interval_id != null) {
+ clearInterval(this.interval_id);
+ }
+ if (imap) { imap.destroy(); }
+ });
+
+ node.emit("input",{});
+}
+RED.nodes.registerType("imap",ImapNode);
diff --git a/dgbuilder/core_nodes/deprecated/73-parsexml.html b/dgbuilder/core_nodes/deprecated/73-parsexml.html
new file mode 100644
index 00000000..b6fc16fe
--- /dev/null
+++ b/dgbuilder/core_nodes/deprecated/73-parsexml.html
@@ -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.
+-->
+
+<script type="text/x-red" data-template-name="xml2js">
+ <!-- <div class="form-row">
+ <label>Use Console</label>
+ <input type="checkbox" id="node-input-useEyes" placeholder="Name" style="display: inline-block; width: auto; vertical-align: top;">
+ <label for="node-input-useEyes" style="width: 70%;">Debug output in console ?</label>
+ </div> -->
+ <div class="form-row">
+ <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
+ <input type="text" id="node-input-name" placeholder="Name">
+ </div>
+ <!-- <div class="form-tips">Uses xml2js to process xml into javascript object.</div> -->
+</script>
+
+<script type="text/x-red" data-help-name="xml2js">
+ <p>A function that parses the <b>msg.payload</b> using the xml2js library. Places the result in the payload.</p>
+ <p>See <a href="https://github.com/Leonidas-from-XIV/node-xml2js/blob/master/README.md" target="_new">the xml2js docs <i>here</i></a> for more information.</p>
+</script>
+
+<script type="text/javascript">
+ RED.nodes.registerType('xml2js',{
+ category: 'deprecated',
+ color:"#E6E0F8",
+ defaults: {
+ //useEyes: {value:false},
+ name: {value:""}
+ },
+ inputs:1,
+ outputs:1,
+ icon: "arrow-in.png",
+ label: function() {
+ return this.name||"xml2json";
+ },
+ labelStyle: function() {
+ return this.name?"node_label_italic":"";
+ }
+ });
+</script>
diff --git a/dgbuilder/core_nodes/deprecated/73-parsexml.js b/dgbuilder/core_nodes/deprecated/73-parsexml.js
new file mode 100644
index 00000000..92850cb6
--- /dev/null
+++ b/dgbuilder/core_nodes/deprecated/73-parsexml.js
@@ -0,0 +1,47 @@
+/**
+ * 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.
+ **/
+
+module.exports = function(RED) {
+ "use strict";
+ var util = require("util");
+ var parseString = require('xml2js').parseString;
+ var useColors = true;
+ //util.inspect.styles.boolean = "red";
+
+ function Xml2jsNode(n) {
+ RED.nodes.createNode(this,n);
+ this.warn("This node has been deprecated and will be deleted in a future release. Please update your flow to use the 'xml' node.");
+ this.useEyes = n.useEyes||false;
+ var node = this;
+ this.on("input", function(msg) {
+ try {
+ parseString(msg.payload, {strict:true,async:true}, function (err, result) {
+ //parseString(msg.payload, {strict:false,async:true}, function (err, result) {
+ if (err) { node.error(err); }
+ else {
+ msg.payload = result;
+ node.send(msg);
+ if (node.useEyes == true) {
+ node.log("\n"+util.inspect(msg, {colors:useColors, depth:10}));
+ }
+ }
+ });
+ }
+ catch(e) { util.log("[73-parsexml.js] "+e); }
+ });
+ }
+ RED.nodes.registerType("xml2js",Xml2jsNode);
+}
diff --git a/dgbuilder/core_nodes/deprecated/74-js2xml.html b/dgbuilder/core_nodes/deprecated/74-js2xml.html
new file mode 100644
index 00000000..f614579b
--- /dev/null
+++ b/dgbuilder/core_nodes/deprecated/74-js2xml.html
@@ -0,0 +1,51 @@
+<!--
+ 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="json2xml">
+ <div class="form-row">
+ <label for="node-input-name"><i class="fa fa-list"></i> XML Root</label>
+ <input type="text" id="node-input-root" placeholder="object"></input>
+ </div>
+ <div class="form-row">
+ <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
+ <input type="text" id="node-input-name" placeholder="Name"></input>
+ </div>
+</script>
+
+<script type="text/x-red" data-help-name="json2xml">
+ <p>A function that parses the <b>msg.payload</b> using the js2xmlparser library. Places the result back in <b>msg.payload</b>.</p>
+ <p>See the <a href="https://github.com/michaelkourlas/node-js2xmlparser" target="_new">js2xmlparser docs</a> for more information.</p>
+</script>
+
+<script type="text/javascript">
+ RED.nodes.registerType('json2xml',{
+ category: 'deprecated',
+ color:"#E6E0F8",
+ defaults: {
+ name: {value:""},
+ root: {value:"object"},
+ },
+ inputs:1,
+ outputs:1,
+ icon: "arrow-in.png",
+ label: function() {
+ return this.name||"json2xml";
+ },
+ labelStyle: function() {
+ return this.name?"node_label_italic":"";
+ }
+ });
+</script>
diff --git a/dgbuilder/core_nodes/deprecated/74-js2xml.js b/dgbuilder/core_nodes/deprecated/74-js2xml.js
new file mode 100644
index 00000000..164bafad
--- /dev/null
+++ b/dgbuilder/core_nodes/deprecated/74-js2xml.js
@@ -0,0 +1,39 @@
+/**
+ * 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.
+ **/
+
+module.exports = function(RED) {
+ "use strict";
+ var js2xmlparser = require("js2xmlparser");
+
+ function Js2XmlNode(n) {
+ RED.nodes.createNode(this,n);
+ this.warn("This node has been deprecated and will be deleted in a future release. Please update your flow to use the 'xml' node.");
+ this.root = n.root;
+ var node = this;
+
+ this.on("input", function(msg) {
+ try {
+ var root = node.root || typeof msg.payload;
+ if (typeof msg.payload !== "object") { msg.payload = '"'+msg.payload+'"'; }
+ console.log(root, typeof msg.payload,msg.payload);
+ msg.payload = js2xmlparser(root, msg.payload);
+ node.send(msg);
+ }
+ catch(e) { console.log(e); }
+ });
+ }
+ RED.nodes.registerType("json2xml",Js2XmlNode);
+}
diff --git a/dgbuilder/core_nodes/deprecated/90-httpget.html b/dgbuilder/core_nodes/deprecated/90-httpget.html
new file mode 100644
index 00000000..b1f2e080
--- /dev/null
+++ b/dgbuilder/core_nodes/deprecated/90-httpget.html
@@ -0,0 +1,61 @@
+<!--
+ 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="httpget">
+ <div class="form-tips"><b>Deprecated</b>: please use the <i>http request</i> node.</div>
+ <br>
+ <div class="form-row">
+ <label for="node-input-baseurl"><i class="fa fa-tasks"></i> Base URL</label>
+ <input type="text" id="node-input-baseurl" placeholder="http(s)://url">
+ </div>
+ <div class="form-row">
+ <label for="node-input-append"><i class="fa fa-tasks"></i> Append</label>
+ <input type="text" id="node-input-append" placeholder="">
+ </div>
+ <div class="form-row">
+ <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
+ <input type="text" id="node-input-name" placeholder="Name">
+ </div>
+ <div class="form-tips">The <b>Base URL</b> gets prepended to whatever payload is passed in. Leave blank if you pass in a full url.<br/>The append gets added to the end after any payload.<br/>The output Topic is the same as the input Topic.</div>
+</script>
+
+<script type="text/x-red" data-help-name="httpget">
+ <p>Performs an HTTP or HTTPS GET and returns the fetched page.</p>
+ <p>The return code is placed in <b>msg.rc</b>, and the full text of the result is in <b>msg.payload</b>.</p>
+ <p>The <b>msg.payload</b> is added to the base url, and then the optional append is added after.</p>
+ <p>This is mostly suitable for small pages as large results will need a lot of parsing....</p>
+</script>
+
+<script type="text/javascript">
+ RED.nodes.registerType('httpget',{
+ category: 'deprecated',
+ color:"rgb(231, 231, 174)",
+ defaults: {
+ name: {value:""},
+ baseurl: {value:""},
+ append: {value:""}
+ },
+ inputs:1,
+ outputs:1,
+ icon: "white-globe.png",
+ label: function() {
+ return this.name||this.baseurl;
+ },
+ labelStyle: function() {
+ return this.name?"node_label_italic":"";
+ }
+ });
+</script>
diff --git a/dgbuilder/core_nodes/deprecated/90-httpget.js b/dgbuilder/core_nodes/deprecated/90-httpget.js
new file mode 100644
index 00000000..63e16b93
--- /dev/null
+++ b/dgbuilder/core_nodes/deprecated/90-httpget.js
@@ -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(process.env.NODE_RED_HOME+"/red/red");
+
+function HttpGet(n) {
+ RED.nodes.createNode(this,n);
+ this.warn("This node has been deprecated and will be deleted in a future release. Please update your flow to use the 'http request' node.");
+ this.baseurl = n.baseurl || "";
+ this.append = n.append || "";
+ var node = this;
+ if (this.baseurl.substring(0,5) === "https") { var http = require("https"); }
+ else { var http = require("http"); }
+ this.on("input", function(msg) {
+ msg._payload = msg.payload;
+ //util.log("[httpget] "+this.baseurl+msg.payload+this.append);
+ http.get(this.baseurl+msg.payload+this.append, function(res) {
+ node.log("Http response: " + res.statusCode);
+ msg.rc = res.statusCode;
+ msg.payload = "";
+ if ((msg.rc != 200) && (msg.rc != 404)) {
+ node.send(msg);
+ }
+ res.setEncoding('utf8');
+ res.on('data', function(chunk) {
+ msg.payload += chunk;
+ });
+ res.on('end', function() {
+ node.send(msg);
+ });
+ }).on('error', function(e) {
+ //node.error(e);
+ msg.rc = 503;
+ msg.payload = e;
+ node.send(msg);
+ });
+ });
+}
+
+RED.nodes.registerType("httpget",HttpGet);
diff --git a/dgbuilder/core_nodes/hardware/35-arduino.html b/dgbuilder/core_nodes/hardware/35-arduino.html
new file mode 100644
index 00000000..17f02892
--- /dev/null
+++ b/dgbuilder/core_nodes/hardware/35-arduino.html
@@ -0,0 +1,171 @@
+<!--
+ Copyright 2013,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="arduino in">
+ <div class="form-row">
+ <label for="node-input-arduino"><i class="fa fa-tasks"></i> Arduino</label>
+ <input type="text" id="node-input-arduino">
+ </div>
+ <div class="form-row">
+ <label for="node-input-pin"><i class="fa fa-circle"></i> Pin</label>
+ <input type="text" id="node-input-pin" placeholder="2">
+ </div>
+ <div class="form-row">
+ <label for="node-input-state"><i class="fa fa-wrench"></i> Type</label>
+ <select type="text" id="node-input-state" style="width: 150px;">
+ <option value="INPUT">Digital pin</option>
+ <option value="ANALOG">Analogue pin</option>
+ </select>
+ </div>
+ <div class="form-row">
+ <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
+ <input type="text" id="node-input-name" placeholder="Name">
+ </div>
+ <div class="form-tips"><b>Note:</b> You cannot use the same pin for both output and input.</div>
+</script>
+
+<script type="text/x-red" data-help-name="arduino in">
+ <p>Arduino input node. Connects to local Arduino and monitors the selected pin for changes. Uses <a href="http://firmata.org/" target="_new"><i>Firmata</i>.</a></p>
+ <p>The Arduino must be loaded with the Standard Firmata sketch available in the Arduino examples.</p>
+ <p>You can select either Digital or Analogue input. Outputs the value read as <b>msg.payload</b> and the pin number as <b>msg.topic</b>.</p>
+ <p>It only outputs on a change of value - fine for digital inputs, but you can get a lot of data from analogue pins which you must then handle.</p>
+ <p>You can set the sample rate in ms from 20 to 65535.</p>
+</script>
+
+<script type="text/javascript">
+ RED.nodes.registerType('arduino in',{
+ category: 'Arduino',
+ color:"#3fadb5",
+ defaults: {
+ name: {value:""},
+ pin: {value:"",required:true},
+ state: {value:"INPUT",required:true},
+ arduino: {type:"arduino-board"}
+ },
+ inputs:0,
+ outputs:1,
+ icon: "arduino.png",
+ label: function() {
+ var a = "";
+ if (this.state == "ANALOG") a = "A";
+ return this.name||"Pin: "+a+this.pin;
+ },
+ labelStyle: function() {
+ return this.name?"node_label_italic":"";
+ }
+ });
+</script>
+
+<script type="text/x-red" data-template-name="arduino out">
+ <div class="form-row">
+ <label for="node-input-arduino"><i class="fa fa-tasks"></i> Arduino</label>
+ <input type="text" id="node-input-arduino">
+ </div>
+ <div class="form-row">
+ <label for="node-input-pin"><i class="fa fa-circle"></i> Pin</label>
+ <input type="text" id="node-input-pin" placeholder="13">
+ </div>
+ <div class="form-row">
+ <label for="node-input-state"><i class="fa fa-wrench"></i> Type</label>
+ <select type="text" id="node-input-state" style="width: 200px;">
+ <option value="OUTPUT">Digital (0/1)</option>
+ <option value="PWM">Analogue (0-255)</option>
+ <option value="SERVO">Servo (0-180)</option>
+ </select>
+ </div>
+ <div class="form-row">
+ <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
+ <input type="text" id="node-input-name" placeholder="Name">
+ </div>
+ <div class="form-tips"><b>Note:</b> You cannot use the same pin for both output and input.</div>
+</script>
+
+<script type="text/x-red" data-help-name="arduino out">
+ <p>Arduino output node. Connects to local Arduino and writes to the selected digital pin. Uses <a href="http://firmata.org/" target="_new"><i>Firmata</i>.</a></p>
+ <p>The Arduino must be loaded with the Standard Firmata sketch available in the Arduino examples.</p>
+ <p>You can select Digital, Analogue (PWM) or Servo type outputs. Expects a numeric value in <b>msg.payload</b>. The pin number is set in the properties panel.</p>
+</script>
+
+<script type="text/javascript">
+ RED.nodes.registerType('arduino out',{
+ category: 'Arduino',
+ color:"#3fadb5",
+ defaults: {
+ name: {value:""},
+ pin: {value:"",required:true},
+ state: {value:"",required:true},
+ arduino: {type:"arduino-board"}
+ },
+ inputs:1,
+ outputs:0,
+ icon: "arduino.png",
+ align: "right",
+ 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="arduino-board">
+ <div class="form-row">
+ <label for="node-config-input-device"><i class="fa fa-random"></i> Port</label>
+ <input type="text" id="node-config-input-device" style="width:60%;" placeholder="e.g. /dev/ttyUSB0 COM1"/>
+ <a id="node-config-lookup-serial" class="btn"><i id="node-config-lookup-serial-icon" class="fa fa-search"></i></a>
+ </div>
+ <div class="form-tips"><b>Tip:</b> Use search to try to auto-detect serial port.</div>
+</script>
+
+<script type="text/javascript">
+ RED.nodes.registerType('arduino-board',{
+ category: 'config',
+ defaults: {
+ device: {value:"",required:true}
+ },
+ label: function() {
+ return this.device||"arduino";
+ },
+ oneditprepare: function() {
+ try {
+ $("#node-config-input-device").autocomplete( "destroy" );
+ } catch(err) { }
+ $("#node-config-lookup-serial").click(function() {
+ $("#node-config-lookup-serial-icon").removeClass('fa-search');
+ $("#node-config-lookup-serial-icon").addClass('spinner');
+ $("#node-config-lookup-serial").addClass('disabled');
+
+ $.getJSON('arduinoports',function(data) {
+ $("#node-config-lookup-serial-icon").addClass('fa-search');
+ $("#node-config-lookup-serial-icon").removeClass('spinner');
+ $("#node-config-lookup-serial").removeClass('disabled');
+ var ports = [];
+ $.each(data, function(i, port){
+ ports.push(port);
+ });
+ $("#node-config-input-device").autocomplete({
+ source:ports,
+ minLength:0,
+ close: function( event, ui ) {
+ $("#node-config-input-device").autocomplete( "destroy" );
+ }
+ }).autocomplete("search","");
+ });
+ });
+ }
+ });
+</script>
diff --git a/dgbuilder/core_nodes/hardware/35-arduino.js b/dgbuilder/core_nodes/hardware/35-arduino.js
new file mode 100644
index 00000000..795e9907
--- /dev/null
+++ b/dgbuilder/core_nodes/hardware/35-arduino.js
@@ -0,0 +1,160 @@
+/**
+ * Copyright 2013,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.
+ **/
+
+module.exports = function(RED) {
+ "use strict";
+ var util = require("util");
+ var ArduinoFirmata = require('arduino-firmata');
+ var fs = require('fs');
+ var plat = require('os').platform();
+ var portlist = ArduinoFirmata.list(function (err, ports) {
+ portlist = ports;
+ });
+
+ // The Board Definition - this opens (and closes) the connection
+ function ArduinoNode(n) {
+ RED.nodes.createNode(this,n);
+ this.device = n.device || null;
+ this.repeat = n.repeat||25;
+ //node.log("opening connection "+this.device);
+ var node = this;
+ node.board = new ArduinoFirmata();
+ if (portlist.indexOf(node.device) === -1) {
+ node.warn("Device "+node.device+" not found");
+ }
+ else {
+ node.board.connect(node.device);
+ }
+
+ node.board.on('boardReady', function(){
+ node.log("version "+node.board.boardVersion);
+ });
+
+ node.on('close', function() {
+ if (node.board) {
+ try {
+ node.board.close(function() {
+ node.log("port closed");
+ });
+ } catch(e) { }
+ }
+ });
+ }
+ RED.nodes.registerType("arduino-board",ArduinoNode);
+
+
+ // The Input Node
+ function DuinoNodeIn(n) {
+ RED.nodes.createNode(this,n);
+ this.buttonState = -1;
+ this.pin = n.pin;
+ this.state = n.state;
+ this.arduino = n.arduino;
+ this.serverConfig = RED.nodes.getNode(this.arduino);
+ if (typeof this.serverConfig === "object") {
+ this.board = this.serverConfig.board;
+ //this.repeat = this.serverConfig.repeat;
+ var node = this;
+ node.status({fill:"red",shape:"ring",text:"connecting"});
+
+ node.board.on('connect', function() {
+ node.status({fill:"green",shape:"dot",text:"connected"});
+ //console.log("i",node.state,node.pin);
+ if (node.state == "ANALOG") {
+ node.board.on('analogChange', function(e) {
+ if (e.pin == node.pin) {
+ var msg = {payload:e.value, topic:"A"+e.pin};
+ node.send(msg);
+ }
+ });
+
+ }
+ else {
+ node.board.pinMode(node.pin, ArduinoFirmata.INPUT);
+ node.board.on('digitalChange', function(e) {
+ if (e.pin == node.pin) {
+ var msg = {payload:e.value, topic:e.pin};
+ node.send(msg);
+ }
+ });
+ }
+ });
+ }
+ else {
+ util.log("[Firmata-arduino] port not configured");
+ }
+ }
+ RED.nodes.registerType("arduino in",DuinoNodeIn);
+
+
+ // The Output Node
+ function DuinoNodeOut(n) {
+ RED.nodes.createNode(this,n);
+ this.buttonState = -1;
+ this.pin = n.pin;
+ this.state = n.state;
+ this.arduino = n.arduino;
+ this.serverConfig = RED.nodes.getNode(this.arduino);
+ if (typeof this.serverConfig === "object") {
+ this.board = this.serverConfig.board;
+ var node = this;
+ node.status({fill:"red",shape:"ring",text:"connecting"});
+
+ node.board.on('connect', function() {
+ node.status({fill:"green",shape:"dot",text:"connected"});
+ //console.log("o",node.state,node.pin);
+ node.board.pinMode(node.pin, node.state);
+ node.on("input", function(msg) {
+ if (node.state == "OUTPUT") {
+ if ((msg.payload == true)||(msg.payload == 1)||(msg.payload.toString().toLowerCase() == "on")) {
+ node.board.digitalWrite(node.pin, true);
+ }
+ if ((msg.payload == false)||(msg.payload == 0)||(msg.payload.toString().toLowerCase() == "off")) {
+ node.board.digitalWrite(node.pin, false);
+ }
+ }
+ if (node.state == "PWM") {
+ msg.payload = msg.payload * 1;
+ if ((msg.payload >= 0) && (msg.payload <= 255)) {
+ //console.log(msg.payload, node.pin);
+ node.board.servoWrite(node.pin, msg.payload);
+ }
+ }
+ if (node.state == "SERVO") {
+ msg.payload = msg.payload * 1;
+ if ((msg.payload >= 0) && (msg.payload <= 180)) {
+ //console.log(msg.payload, node.pin);
+ node.board.servoWrite(node.pin, msg.payload);
+ }
+ }
+ });
+ });
+ }
+ else {
+ util.log("[Firmata-arduino] port not configured");
+ }
+ }
+ RED.nodes.registerType("arduino out",DuinoNodeOut);
+
+ RED.httpAdmin.get("/arduinoports",function(req,res) {
+ ArduinoFirmata.list(function (err, ports) {
+ //console.log(JSON.stringify(ports));
+ res.writeHead(200, {'Content-Type': 'text/plain'});
+ res.write(JSON.stringify(ports));
+ res.end();
+ });
+ });
+}
diff --git a/dgbuilder/core_nodes/hardware/36-rpi-gpio.html b/dgbuilder/core_nodes/hardware/36-rpi-gpio.html
new file mode 100644
index 00000000..9e705c2c
--- /dev/null
+++ b/dgbuilder/core_nodes/hardware/36-rpi-gpio.html
@@ -0,0 +1,182 @@
+<!--
+ Copyright 2013,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-gpio in">
+ <div class="form-row">
+ <label for="node-input-pin"><i class="fa fa-circle"></i> GPIO Pin</label>
+ <select type="text" id="node-input-pin" style="width: 200px;">
+ <option value="-" disabled>select pin </option>
+ <option value="3">3 - SDA1 </option>
+ <option value="5">5 - SCL1 </option>
+ <option value="7">7 - GPIO7</option>
+ <option value="8">8 - TxD </option>
+ <option value="10">10 - RxD </option>
+ <option value="11">11 - GPIO0</option>
+ <option value="12">12 - GPIO1</option>
+ <option value="13">13 - GPIO2</option>
+ <option value="15">15 - GPIO3</option>
+ <option value="16">16 - GPIO4</option>
+ <option value="18">18 - GPIO5</option>
+ <option value="19">19 - MOSI </option>
+ <option value="21">21 - MISO </option>
+ <option value="22">22 - GPIO6</option>
+ <option value="23">23 - SCLK </option>
+ <option value="24">24 - CE0 </option>
+ <option value="26">26 - CE1 </option>
+ </select>
+ &nbsp;<span id="pitype"></span>
+ </div>
+ <div class="form-row">
+ <label for="node-input-intype"><i class="fa fa-long-arrow-up"></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="fa fa-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":"";
+ },
+ oneditprepare: function() {
+ $.getJSON('rpi-gpio/'+this.id,function(data) {
+ $('#pitype').text(data.type);
+ if (data.type === "Model B+") {
+ $('#node-input-pin').append($("<option></option>").attr("value",27).text("27 - SDA0"));
+ $('#node-input-pin').append($("<option></option>").attr("value",28).text("28 - SCL0"));
+ $('#node-input-pin').append($("<option></option>").attr("value",29).text("29 - GPIO21"));
+ $('#node-input-pin').append($("<option></option>").attr("value",31).text("31 - GPIO22"));
+ $('#node-input-pin').append($("<option></option>").attr("value",32).text("32 - GPIO26"));
+ $('#node-input-pin').append($("<option></option>").attr("value",33).text("33 - GPIO23"));
+ $('#node-input-pin').append($("<option></option>").attr("value",35).text("35 - GPIO24"));
+ $('#node-input-pin').append($("<option></option>").attr("value",36).text("36 - GPIO27"));
+ $('#node-input-pin').append($("<option></option>").attr("value",37).text("37 - GPIO25"));
+ $('#node-input-pin').append($("<option></option>").attr("value",38).text("38 - GPIO28"));
+ $('#node-input-pin').append($("<option></option>").attr("value",40).text("40 - GPIO29"));
+ }
+ });
+ }
+ });
+</script>
+
+
+<script type="text/x-red" data-template-name="rpi-gpio out">
+ <div class="form-row">
+ <label for="node-input-pin"><i class="fa fa-circle"></i> GPIO Pin</label>
+ <select type="text" id="node-input-pin" style="width: 200px;">
+ <option value="-">select pin </option>
+ <option value="3">3 - SDA1 </option>
+ <option value="5">5 - SCL1 </option>
+ <option value="7">7 - GPIO7</option>
+ <option value="8">8 - TxD </option>
+ <option value="10">10 - RxD </option>
+ <option value="11">11 - GPIO0</option>
+ <option value="12">12 - GPIO1</option>
+ <option value="13">13 - GPIO2</option>
+ <option value="15">15 - GPIO3</option>
+ <option value="16">16 - GPIO4</option>
+ <option value="18">18 - GPIO5</option>
+ <option value="19">19 - MOSI </option>
+ <option value="21">21 - MISO </option>
+ <option value="22">22 - GPIO6</option>
+ <option value="23">23 - SCLK </option>
+ <option value="24">24 - CE0 </option>
+ <option value="26">26 - CE1 </option>
+ </select>
+ &nbsp;<span id="pitype"></span>
+ </div>
+ <div class="form-row">
+ <label for="node-input-name"><i class="fa fa-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":"";
+ },
+ oneditprepare: function() {
+ $.getJSON('rpi-gpio/'+this.id,function(data) {
+ $('#pitype').text(data.type);
+ if (data.type === "Model B+") {
+ $('#node-input-pin').append($("<option></option>").attr("value",27).text("27 - SDA0"));
+ $('#node-input-pin').append($("<option></option>").attr("value",28).text("28 - SCL0"));
+ $('#node-input-pin').append($("<option></option>").attr("value",29).text("29 - GPIO21"));
+ $('#node-input-pin').append($("<option></option>").attr("value",31).text("31 - GPIO22"));
+ $('#node-input-pin').append($("<option></option>").attr("value",32).text("32 - GPIO26"));
+ $('#node-input-pin').append($("<option></option>").attr("value",33).text("33 - GPIO23"));
+ $('#node-input-pin').append($("<option></option>").attr("value",35).text("35 - GPIO24"));
+ $('#node-input-pin').append($("<option></option>").attr("value",36).text("36 - GPIO27"));
+ $('#node-input-pin').append($("<option></option>").attr("value",37).text("37 - GPIO25"));
+ $('#node-input-pin').append($("<option></option>").attr("value",38).text("38 - GPIO28"));
+ $('#node-input-pin').append($("<option></option>").attr("value",40).text("40 - GPIO29"));
+ }
+ });
+ }
+ });
+</script>
diff --git a/dgbuilder/core_nodes/hardware/36-rpi-gpio.js b/dgbuilder/core_nodes/hardware/36-rpi-gpio.js
new file mode 100644
index 00000000..93cbc4e0
--- /dev/null
+++ b/dgbuilder/core_nodes/hardware/36-rpi-gpio.js
@@ -0,0 +1,185 @@
+/**
+ * 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.
+ **/
+
+module.exports = function(RED) {
+ "use strict";
+ var util = require("util");
+ var exec = require('child_process').exec;
+ var fs = require('fs');
+
+ var gpioCommand = '/usr/local/bin/gpio';
+
+ if (!fs.existsSync("/dev/ttyAMA0")) { // unlikely if not on a Pi
+ throw "Info : Ignoring Raspberry Pi specific node.";
+ }
+
+ if (!fs.existsSync(gpioCommand)) { // 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
+ "11":"0",
+ "12":"1",
+ "13":"2",
+ "15":"3",
+ "16":"4",
+ "18":"5",
+ "22":"6",
+ "7":"7",
+ "3":"8",
+ "5":"9",
+ "24":"10",
+ "26":"11",
+ "19":"12",
+ "21":"13",
+ "23":"14",
+ "8":"15",
+ "10":"16",
+ "27":"30",
+ "28":"31",
+ "29":"21",
+ "31":"22",
+ "32":"26",
+ "33":"23",
+ "35":"24",
+ "36":"27",
+ "37":"25",
+ "38":"28",
+ "40":"29"
+ }
+ var tablepin = {
+ // WiringPi : Physical
+ "0":"11",
+ "1":"12",
+ "2":"13",
+ "3":"15",
+ "4":"16",
+ "5":"18",
+ "6":"22",
+ "7":"7",
+ "8":"3",
+ "9":"5",
+ "10":"24",
+ "11":"26",
+ "12":"19",
+ "13":"21",
+ "14":"23",
+ "15":"8",
+ "16":"10",
+ "30":"27",
+ "31":"28",
+ "21":"29",
+ "22":"31",
+ "26":"32",
+ "23":"33",
+ "24":"35",
+ "27":"36",
+ "25":"37",
+ "28":"38",
+ "29":"40"
+ }
+
+ function GPIOInNode(n) {
+ RED.nodes.createNode(this,n);
+ this.buttonState = -1;
+ this.pin = pintable[n.pin];
+ this.intype = n.intype;
+ var node = this;
+
+ if (node.pin !== undefined) {
+ exec(gpioCommand+" mode "+node.pin+" "+node.intype, function(err,stdout,stderr) {
+ if (err) { node.error(err); }
+ else {
+ node._interval = setInterval( function() {
+ exec(gpioCommand+" 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 {
+ node.error("Invalid GPIO pin: "+node.pin);
+ }
+
+ node.on("close", function() {
+ clearInterval(node._interval);
+ });
+ }
+
+ function GPIOOutNode(n) {
+ RED.nodes.createNode(this,n);
+ this.pin = pintable[n.pin];
+ var node = this;
+
+ if (node.pin !== undefined) {
+ process.nextTick(function() {
+ exec(gpioCommand+" 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(gpioCommand+" write "+node.pin+" "+out, function(err,stdout,stderr) {
+ if (err) { node.error(err); }
+ });
+ }
+ else { node.warn("Invalid input - not 0 or 1"); }
+ });
+ }
+ });
+ });
+ }
+ else {
+ node.error("Invalid GPIO pin: "+node.pin);
+ }
+
+ node.on("close", function() {
+ exec(gpioCommand+" mode "+node.pin+" in");
+ });
+ }
+
+ var pitype = { type:"" };
+ exec(gpioCommand+" -v | grep Type", function(err,stdout,stderr) {
+ if (err) {
+ util.log('[36-rpi-gpio.js] Error: "'+gpioCommand+' -v" command failed for some reason.');
+ }
+ else {
+ pitype = { type:(stdout.split(","))[0].split(": ")[1], rev:(stdout.split(","))[1].split(": ")[1] };
+ }
+ });
+
+ RED.nodes.registerType("rpi-gpio in",GPIOInNode);
+ RED.nodes.registerType("rpi-gpio out",GPIOOutNode);
+
+ RED.httpAdmin.get('/rpi-gpio/:id',function(req,res) {
+ res.send( JSON.stringify(pitype) );
+ });
+}
diff --git a/dgbuilder/core_nodes/io/10-mqtt.html b/dgbuilder/core_nodes/io/10-mqtt.html
new file mode 100644
index 00000000..2ff5eb29
--- /dev/null
+++ b/dgbuilder/core_nodes/io/10-mqtt.html
@@ -0,0 +1,157 @@
+<!--
+ Copyright 2013,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="mqtt in">
+ <div class="form-row">
+ <label for="node-input-broker"><i class="fa fa-globe"></i> Broker</label>
+ <input type="text" id="node-input-broker">
+ </div>
+ <div class="form-row">
+ <label for="node-input-topic"><i class="fa fa-tasks"></i> Topic</label>
+ <input type="text" id="node-input-topic" placeholder="Topic">
+ </div>
+ <div class="form-row">
+ <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
+ <input type="text" id="node-input-name" placeholder="Name">
+ </div>
+</script>
+
+<script type="text/x-red" data-help-name="mqtt in">
+ <p>MQTT input node. Connects to a broker and subscribes to the specified topic. The topic may contain MQTT wildcards.</p>
+ <p>Outputs an object called <b>msg</b> containing <b>msg.topic, msg.payload, msg.qos</b> and <b>msg.retain</b>.</p>
+ <p><b>msg.payload</b> is a String.</p>
+</script>
+
+<script type="text/javascript">
+ RED.nodes.registerType('mqtt in',{
+ category: 'input',
+ defaults: {
+ name: {value:""},
+ topic: {value:"",required:true},
+ broker: {type:"mqtt-broker", required:true}
+ },
+ color:"#d8bfd8",
+ inputs:0,
+ outputs:1,
+ icon: "bridge.png",
+ label: function() {
+ return this.name||this.topic||"mqtt";
+ },
+ labelStyle: function() {
+ return this.name?"node_label_italic":"";
+ }
+ });
+</script>
+
+<script type="text/x-red" data-template-name="mqtt out">
+ <div class="form-row">
+ <label for="node-input-broker"><i class="fa fa-globe"></i> Broker</label>
+ <input type="text" id="node-input-broker">
+ </div>
+ <div class="form-row">
+ <label for="node-input-topic"><i class="fa fa-tasks"></i> Topic</label>
+ <input type="text" id="node-input-topic" placeholder="Topic">
+ </div>
+ <div class="form-row">
+ <label for="node-input-qos"><i class="fa fa-empire"></i> QoS</label>
+ <select id="node-input-qos" style="width:125px !important">
+ <option value=""></option>
+ <option value="0">0</option>
+ <option value="1">1</option>
+ <option value="2">2</option>
+ </select>
+ &nbsp;&nbsp;<i class="fa fa-history"></i>&nbsp;Retain &nbsp;<select id="node-input-retain" style="width:125px !important">
+ <option value=""></option>
+ <option value="false">false</option>
+ <option value="true">true</option>
+ </select>
+ </div>
+ <div class="form-row">
+ <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
+ <input type="text" id="node-input-name" placeholder="Name">
+ </div>
+ <div class="form-tips">Tip: Leave topic, qos or retain blank if you want to set them via msg properties.</div>
+</script>
+
+<script type="text/x-red" data-help-name="mqtt out">
+ <p>Connects to a MQTT broker and publishes <b>msg.payload</b> either to the <b>msg.topic</b> or to the topic specified in the edit window. The value in the edit window has precedence.</p>
+ <p>Likewise QoS and/or retain values in the edit panel will overwrite any <b>msg.qos</b> and <b>msg.retain</b> properties. If nothing is set they default to <i>0</i> and <i>false</i> respectively.</p>
+ <p>If <b>msg.payload</b> contains an object it will be stringified before being sent.</p>
+</script>
+
+<script type="text/javascript">
+ RED.nodes.registerType('mqtt out',{
+ category: 'output',
+ defaults: {
+ name: {value:""},
+ topic: {value:""},
+ qos: {value:""},
+ retain: {value:""},
+ broker: {type:"mqtt-broker", required:true}
+ },
+ color:"#d8bfd8",
+ inputs:1,
+ outputs:0,
+ icon: "bridge.png",
+ align: "right",
+ label: function() {
+ return this.name||this.topic||"mqtt";
+ },
+ labelStyle: function() {
+ return this.name?"node_label_italic":"";
+ }
+ });
+</script>
+
+<script type="text/x-red" data-template-name="mqtt-broker">
+ <div class="form-row node-input-broker">
+ <label for="node-config-input-broker"><i class="fa fa-globe"></i> Broker</label>
+ <input class="input-append-left" type="text" id="node-config-input-broker" placeholder="localhost" style="width: 40%;" >
+ <label for="node-config-input-port" style="margin-left: 10px; width: 35px; "> Port</label>
+ <input type="text" id="node-config-input-port" placeholder="Port" style="width:45px">
+ </div>
+ <div class="form-row">
+ <label for="node-config-input-clientid"><i class="fa fa-tag"></i> Client ID</label>
+ <input type="text" id="node-config-input-clientid" placeholder="Leave blank for auto generated">
+ </div>
+ <div class="form-row">
+ <label for="node-config-input-user"><i class="fa fa-user"></i> Username</label>
+ <input type="text" id="node-config-input-user">
+ </div>
+ <div class="form-row">
+ <label for="node-config-input-password"><i class="fa fa-lock"></i> Password</label>
+ <input type="password" id="node-config-input-password">
+ </div>
+</script>
+
+<script type="text/javascript">
+ RED.nodes.registerType('mqtt-broker',{
+ category: 'config',
+ defaults: {
+ broker: {value:"",required:true},
+ port: {value:1883,required:true,validate:RED.validators.number()},
+ clientid: { value:"" }
+ },
+ credentials: {
+ user: {type:"text"},
+ password: {type: "password"}
+ },
+ label: function() {
+ if (this.broker == "") { this.broker = "localhost"; }
+ return (this.clientid?this.clientid+"@":"")+this.broker+":"+this.port;
+ }
+ });
+</script>
diff --git a/dgbuilder/core_nodes/io/10-mqtt.js b/dgbuilder/core_nodes/io/10-mqtt.js
new file mode 100644
index 00000000..c8bc4901
--- /dev/null
+++ b/dgbuilder/core_nodes/io/10-mqtt.js
@@ -0,0 +1,119 @@
+/**
+ * Copyright 2013,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.
+ **/
+
+module.exports = function(RED) {
+ "use strict";
+ var connectionPool = require("./lib/mqttConnectionPool");
+
+ function MQTTBrokerNode(n) {
+ RED.nodes.createNode(this,n);
+ this.broker = n.broker;
+ this.port = n.port;
+ this.clientid = n.clientid;
+ if (this.credentials) {
+ this.username = this.credentials.user;
+ this.password = this.credentials.password;
+ }
+ }
+ RED.nodes.registerType("mqtt-broker",MQTTBrokerNode,{
+ credentials: {
+ user: {type:"text"},
+ password: {type: "password"}
+ }
+ });
+
+ function MQTTInNode(n) {
+ RED.nodes.createNode(this,n);
+ this.topic = n.topic;
+ this.broker = n.broker;
+ this.brokerConfig = RED.nodes.getNode(this.broker);
+ if (this.brokerConfig) {
+ this.status({fill:"red",shape:"ring",text:"disconnected"});
+ this.client = connectionPool.get(this.brokerConfig.broker,this.brokerConfig.port,this.brokerConfig.clientid,this.brokerConfig.username,this.brokerConfig.password);
+ var node = this;
+ this.client.subscribe(this.topic,2,function(topic,payload,qos,retain) {
+ var msg = {topic:topic,payload:payload,qos:qos,retain:retain};
+ if ((node.brokerConfig.broker == "localhost")||(node.brokerConfig.broker == "127.0.0.1")) {
+ msg._topic = topic;
+ }
+ node.send(msg);
+ });
+ this.client.on("connectionlost",function() {
+ node.status({fill:"red",shape:"ring",text:"disconnected"});
+ });
+ this.client.on("connect",function() {
+ node.status({fill:"green",shape:"dot",text:"connected"});
+ });
+ this.client.connect();
+ } else {
+ this.error("missing broker configuration");
+ }
+ this.on('close', function() {
+ if (this.client) {
+ this.client.disconnect();
+ }
+ });
+ }
+ RED.nodes.registerType("mqtt in",MQTTInNode);
+
+ function MQTTOutNode(n) {
+ RED.nodes.createNode(this,n);
+ this.topic = n.topic;
+ this.qos = n.qos || null;
+ this.retain = n.retain;
+ this.broker = n.broker;
+ this.brokerConfig = RED.nodes.getNode(this.broker);
+
+ if (this.brokerConfig) {
+ this.status({fill:"red",shape:"ring",text:"disconnected"},true);
+ this.client = connectionPool.get(this.brokerConfig.broker,this.brokerConfig.port,this.brokerConfig.clientid,this.brokerConfig.username,this.brokerConfig.password);
+ var node = this;
+ this.on("input",function(msg) {
+ if (msg.qos) {
+ msg.qos = parseInt(msg.qos);
+ if ((msg.qos !== 0) && (msg.qos !== 1) && (msg.qos !== 2)) {
+ msg.qos = null;
+ }
+ }
+ msg.qos = Number(node.qos || msg.qos || 0);
+ msg.retain = node.retain || msg.retain || false;
+ msg.retain = ((msg.retain === true) || (msg.retain === "true")) || false;
+ if (node.topic) {
+ msg.topic = node.topic;
+ }
+ if ((msg.hasOwnProperty("topic")) && (typeof msg.topic === "string") && (msg.topic !== "")) { // topic must exist
+ this.client.publish(msg); // send the message
+ }
+ else { node.warn("Invalid topic specified"); }
+ });
+ this.client.on("connectionlost",function() {
+ node.status({fill:"red",shape:"ring",text:"disconnected"});
+ });
+ this.client.on("connect",function() {
+ node.status({fill:"green",shape:"dot",text:"connected"});
+ });
+ this.client.connect();
+ } else {
+ this.error("missing broker configuration");
+ }
+ this.on('close', function() {
+ if (this.client) {
+ this.client.disconnect();
+ }
+ });
+ }
+ RED.nodes.registerType("mqtt out",MQTTOutNode);
+}
diff --git a/dgbuilder/core_nodes/io/21-httpin.html b/dgbuilder/core_nodes/io/21-httpin.html
new file mode 100644
index 00000000..059b8596
--- /dev/null
+++ b/dgbuilder/core_nodes/io/21-httpin.html
@@ -0,0 +1,254 @@
+<!--
+ 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="http in">
+ <div class="form-row">
+ <label for="node-input-method"><i class="fa fa-tasks"></i> Method</label>
+ <select type="text" id="node-input-method" style="width:72%;">
+ <option value="get">GET</option>
+ <option value="post">POST</option>
+ <option value="put">PUT</option>
+ <option value="delete">DELETE</option>
+ </select>
+ </div>
+ <div class="form-row">
+ <label for="node-input-url"><i class="fa fa-globe"></i> url</label>
+ <input type="text" id="node-input-url" placeholder="/url">
+ </div>
+ <div class="form-row">
+ <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
+ <input type="text" id="node-input-name" placeholder="Name">
+ </div>
+ <div id="node-input-tip" class="form-tips">The url will be relative to <code><span id="node-input-path"></span></code>.</div>
+</script>
+
+<script type="text/x-red" data-help-name="http in">
+ <p>Provides an input node for http requests, allowing the creation of simple web services.</p>
+ <p>The resulting message has the following properties:
+ <ul>
+ <li>msg.req : <a href="http://expressjs.com/api.html#req">http request</a></li>
+ <li>msg.res : <a href="http://expressjs.com/api.html#res">http response</a></li>
+ </ul>
+ </p>
+ <p>For POST/PUT requests, the body is available under <code>msg.req.body</code>. This
+ uses the <a href="http://expressjs.com/api.html#bodyParser">Express bodyParser middleware</a> to parse the content to a JSON object.
+ </p>
+ <p>
+ By default, this expects the body of the request to be url encoded:
+ <pre>foo=bar&amp;this=that</pre>
+ </p>
+ <p>
+ To send JSON encoded data to the node, the content-type header of the request must be set to
+ <code>application/json</code>.
+ </p>
+ <p>
+ <b>Note: </b>This node does not send any response to the http request. This should be done with
+ a subsequent HTTP Response node, or Function node.
+ In the case of a Function node, the <a href="http://expressjs.com/api.html#res">Express response documentation</a>
+ describes how this should be done. For example:
+ <pre>msg.res.send(200, 'Thanks for the request ');<br/>return msg;</pre>
+ </p>
+
+</script>
+
+<script type="text/x-red" data-template-name="http response">
+ <div class="form-row">
+ <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
+ <input type="text" id="node-input-name" placeholder="Name">
+ </div>
+ <div class="form-tips">The messages sent to this node <b>must</b> originate from an <i>http input</i> node</div>
+</script>
+
+<script type="text/x-red" data-help-name="http response">
+ <p>Sends responses back to http requests received from an HTTP Input node.</p>
+ <p>The response can be customised using the following message properties:</p>
+ <ul>
+ <li><code>payload</code> is sent as the body of the response</li>
+ <li><code>statusCode</code>, if set, is used as the response status code (default: 200)</li>
+ <li><code>headers</code>, if set, should be an object containing field/value
+ pairs to be added as response headers.</li>
+ </ul>
+</script>
+
+<script type="text/x-red" data-template-name="http request">
+ <div class="form-row">
+ <label for="node-input-method"><i class="fa fa-tasks"></i> Method</label>
+ <select type="text" id="node-input-method" style="width:72%;">
+ <option value="GET">GET</option>
+ <option value="POST">POST</option>
+ <option value="PUT">PUT</option>
+ <option value="DELETE">DELETE</option>
+ </select>
+ </div>
+ <div class="form-row">
+ <label for="node-input-url"><i class="fa fa-globe"></i> URL</label>
+ <input type="text" id="node-input-url" placeholder="http://">
+ </div>
+ <div class="form-row">
+ <label>&nbsp;</label>
+ <input type="checkbox" id="node-input-useAuth" style="display: inline-block; width: auto; vertical-align: top;">
+ <label for="node-input-useAuth" style="width: 70%;">Use basic authentication?</label>
+ </div>
+ <div class="form-row node-input-useAuth-row">
+ <label for="node-input-user"><i class="fa fa-user"></i> Username</label>
+ <input type="text" id="node-input-user">
+ </div>
+ <div class="form-row node-input-useAuth-row">
+ <label for="node-input-password"><i class="fa fa-lock"></i> Password</label>
+ <input type="password" id="node-input-password">
+ </div>
+ <div class="form-row">
+ <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
+ <input type="text" id="node-input-name" placeholder="Name">
+ </div>
+</script>
+
+<script type="text/x-red" data-help-name="http request">
+ <p>Provides a node for making http requests.</p>
+ <p>The URL and HTTP method can be configured in the node, but also
+ overridden by the incoming message:
+ <ul>
+ <li><code>url</code>, if set, is used as the url of the request. Must start with http: or https:</li>
+ <li><code>method</code>, if set, is used as the HTTP method of the request.
+ Must be one of <code>GET</code>, <code>PUT</code>, <code>POST</code> or <code>DELETE</code> (default: GET)</li>
+ <li><code>headers</code>, if set, should be an object containing field/value
+ pairs to be added as request headers</li>
+ <li><code>payload</code> is sent as the body of the request</li>
+ </ul>
+ <p>When configured within the node, the URL property can contain <a href="http://mustache.github.io/mustache.5.html" target="_new">mustache-style</a> tags. These allow the
+ url to be constructed using values of the incoming message. For example, if the url is set to
+ <code>example.com/{{topic}}</code>, it will have the value of <code>msg.topic</code> automatically inserted.</p>
+ <p>
+ The output message contains the following properties:
+ <ul>
+ <li><code>payload</code> is the body of the response</li>
+ <li><code>statusCode</code> is the status code of the response, or the error code if the request could not be completed</li>
+ <li><code>headers</code> is an object containing the response headers</li>
+ </ul>
+</script>
+
+<script type="text/javascript">
+ RED.nodes.registerType('http in',{
+ category: 'input',
+ color:"rgb(231, 231, 174)",
+ defaults: {
+ name: {value:""},
+ url: {value:"",required:true},
+ method: {value:"get",required:true}
+ },
+ inputs:0,
+ outputs:1,
+ icon: "white-globe.png",
+ label: function() {
+ if (this.name) {
+ return this.name;
+ } else if (this.url) {
+ var root = RED.settings.httpNodeRoot;
+ if (root.slice(-1) != "/") {
+ root = root+"/";
+ }
+ if (this.url.charAt(0) == "/") {
+ root += this.url.slice(1);
+ } else {
+ root += this.url;
+ }
+ return "["+this.method+"] "+root;
+ } else {
+ return "http";
+ }
+ },
+ labelStyle: function() {
+ return this.name?"node_label_italic":"";
+ },
+ oneditprepare: function() {
+ var root = RED.settings.httpNodeRoot;
+ if (root.slice(-1) == "/") {
+ root = root.slice(0,-1);
+ }
+ if (root == "") {
+ $("#node-input-tip").hide();
+ } else {
+ $("#node-input-path").html(root);
+ $("#node-input-tip").show();
+ }
+ //document.getElementById("node-config-wsdocpath").innerHTML=
+ }
+
+ });
+
+ RED.nodes.registerType('http response',{
+ category: 'output',
+ color:"rgb(231, 231, 174)",
+ defaults: {
+ name: {value:""}
+ },
+ inputs:1,
+ outputs:0,
+ align: "right",
+ icon: "white-globe.png",
+ label: function() {
+ return this.name||"http";
+ },
+ labelStyle: function() {
+ return this.name?"node_label_italic":"";
+ }
+ });
+
+ RED.nodes.registerType('http request',{
+ category: 'function',
+ color:"rgb(231, 231, 174)",
+ defaults: {
+ name: {value:""},
+ method:{value:"GET"},
+ url:{value:""},
+ //user -> credentials
+ //pass -> credentials
+ },
+ credentials: {
+ user: {type:"text"},
+ password: {type: "password"}
+ },
+ inputs:1,
+ outputs:1,
+ align: "right",
+ icon: "white-globe.png",
+ label: function() {
+ return this.name||"http request";
+ },
+ labelStyle: function() {
+ return this.name?"node_label_italic":"";
+ },
+ oneditprepare: function() {
+ if (this.credentials.user || this.credentials.has_password) {
+ $('#node-input-useAuth').prop('checked', true);
+ $(".node-input-useAuth-row").show();
+ } else {
+ $('#node-input-useAuth').prop('checked', false);
+ $(".node-input-useAuth-row").hide();
+ }
+
+ $("#node-input-useAuth").change(function() {
+ if ($(this).is(":checked")) {
+ $(".node-input-useAuth-row").show();
+ } else {
+ $(".node-input-useAuth-row").hide();
+ $('#node-input-user').val('');
+ $('#node-input-password').val('');
+ }
+ });
+ },
+ });
+</script>
diff --git a/dgbuilder/core_nodes/io/21-httpin.js b/dgbuilder/core_nodes/io/21-httpin.js
new file mode 100644
index 00000000..877ccc09
--- /dev/null
+++ b/dgbuilder/core_nodes/io/21-httpin.js
@@ -0,0 +1,241 @@
+/**
+ * Copyright 2013,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.
+ **/
+
+module.exports = function(RED) {
+ "use strict";
+ var http = require("follow-redirects").http;
+ var https = require("follow-redirects").https;
+ var urllib = require("url");
+ var express = require("express");
+ var getBody = require('raw-body');
+ var mustache = require("mustache");
+ var querystring = require("querystring");
+
+ var cors = require('cors');
+ var jsonParser = express.json();
+ var urlencParser = express.urlencoded();
+
+ function rawBodyParser(req, res, next) {
+ if (req._body) { return next(); }
+ req.body = "";
+ req._body = true;
+ getBody(req, {
+ limit: '1mb',
+ length: req.headers['content-length'],
+ encoding: 'utf8'
+ }, function (err, buf) {
+ if (err) { return next(err); }
+ req.body = buf;
+ next();
+ });
+ }
+
+
+ function HTTPIn(n) {
+ RED.nodes.createNode(this,n);
+ if (RED.settings.httpNodeRoot !== false) {
+
+ this.url = n.url;
+ this.method = n.method;
+
+ var node = this;
+
+ this.errorHandler = function(err,req,res,next) {
+ node.warn(err);
+ res.send(500);
+ };
+
+ this.callback = function(req,res) {
+ if (node.method == "post") {
+ node.send({req:req,res:res,payload:req.body});
+ } else if (node.method == "get") {
+ node.send({req:req,res:res,payload:req.query});
+ } else {
+ node.send({req:req,res:res});
+ }
+ }
+
+ var corsHandler = function(req,res,next) { next(); }
+
+ if (RED.settings.httpNodeCors) {
+ corsHandler = cors(RED.settings.httpNodeCors);
+ RED.httpNode.options(this.url,corsHandler);
+ }
+
+ if (this.method == "get") {
+ RED.httpNode.get(this.url,corsHandler,this.callback,this.errorHandler);
+ } else if (this.method == "post") {
+ RED.httpNode.post(this.url,corsHandler,jsonParser,urlencParser,rawBodyParser,this.callback,this.errorHandler);
+ } else if (this.method == "put") {
+ RED.httpNode.put(this.url,corsHandler,jsonParser,urlencParser,rawBodyParser,this.callback,this.errorHandler);
+ } else if (this.method == "delete") {
+ RED.httpNode.delete(this.url,corsHandler,this.callback,this.errorHandler);
+ }
+
+ this.on("close",function() {
+ var routes = RED.httpNode.routes[this.method];
+ for (var i = 0; i<routes.length; i++) {
+ if (routes[i].path == this.url) {
+ routes.splice(i,1);
+ //break;
+ }
+ }
+ if (RED.settings.httpNodeCors) {
+ var route = RED.httpNode.route['options'];
+ for (var j = 0; j<route.length; j++) {
+ if (route[j].path == this.url) {
+ route.splice(j,1);
+ //break;
+ }
+ }
+ }
+ });
+ } else {
+ this.warn("Cannot create http-in node when httpNodeRoot set to false");
+ }
+ }
+ 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) {
+ msg.res.set(msg.headers);
+ }
+ var statusCode = msg.statusCode || 200;
+ if (typeof msg.payload == "object" && !Buffer.isBuffer(msg.payload)) {
+ msg.res.jsonp(statusCode,msg.payload);
+ } else {
+ if (msg.res.get('content-length') == null) {
+ var len;
+ if (msg.payload == null) {
+ len = 0;
+ } else if (typeof msg.payload == "number") {
+ len = Buffer.byteLength(""+msg.payload);
+ } else {
+ len = Buffer.byteLength(msg.payload);
+ }
+ msg.res.set('content-length', len);
+ }
+ msg.res.send(statusCode,msg.payload);
+ }
+ } else {
+ node.warn("No response object");
+ }
+ });
+ }
+ RED.nodes.registerType("http response",HTTPOut);
+
+ function HTTPRequest(n) {
+ RED.nodes.createNode(this,n);
+ var nodeUrl = n.url;
+ var isTemplatedUrl = (nodeUrl||"").indexOf("{{") != -1;
+ var nodeMethod = n.method || "GET";
+ var node = this;
+ this.on("input",function(msg) {
+ node.status({fill:"blue",shape:"dot",text:"requesting"});
+ var url;
+ if (msg.url) {
+ url = msg.url;
+ } else if (isTemplatedUrl) {
+ url = mustache.render(nodeUrl,msg);
+ } else {
+ url = nodeUrl;
+ }
+ // url must start http:// or https:// so assume http:// if not set
+ if (!((url.indexOf("http://")===0) || (url.indexOf("https://")===0))) {
+ url = "http://"+url;
+ }
+
+ var method = (msg.method||nodeMethod).toUpperCase();
+ //node.log(method+" : "+url);
+ var opts = urllib.parse(url);
+ opts.method = method;
+ opts.headers = {};
+ if (msg.headers) {
+ for (var v in msg.headers) {
+ if (msg.headers.hasOwnProperty(v)) {
+ var name = v.toLowerCase();
+ if (name !== "content-type" && name !== "content-length") {
+ // only normalise the known headers used later in this
+ // function. Otherwise leave them alone.
+ name = v;
+ }
+ opts.headers[name] = msg.headers[v];
+ }
+ }
+ }
+ if (this.credentials && this.credentials.user) {
+ opts.auth = this.credentials.user+":"+(this.credentials.password||"");
+ }
+ var payload = null;
+
+ if (msg.payload && (method == "POST" || method == "PUT") ) {
+ if (typeof msg.payload === "string" || Buffer.isBuffer(msg.payload)) {
+ payload = msg.payload;
+ } else if (typeof msg.payload == "number") {
+ payload = msg.payload+"";
+ } else {
+ if (opts.headers['content-type'] == 'application/x-www-form-urlencoded') {
+ payload = querystring.stringify(msg.payload);
+ } else {
+ payload = JSON.stringify(msg.payload);
+ if (opts.headers['content-type'] == null) {
+ opts.headers['content-type'] = "application/json";
+ }
+ }
+ }
+ if (opts.headers['content-length'] == null) {
+ opts.headers['content-length'] = Buffer.byteLength(payload);
+ }
+ }
+
+ var req = ((/^https/.test(url))?https:http).request(opts,function(res) {
+ res.setEncoding('utf8');
+ msg.statusCode = res.statusCode;
+ msg.headers = res.headers;
+ msg.payload = "";
+ res.on('data',function(chunk) {
+ msg.payload += chunk;
+ });
+ res.on('end',function() {
+ node.send(msg);
+ node.status({});
+ });
+ });
+ req.on('error',function(err) {
+ msg.payload = err.toString() + " : " + url;
+ msg.statusCode = err.code;
+ node.send(msg);
+ node.status({fill:"red",shape:"ring",text:err.code});
+ });
+ if (payload) {
+ req.write(payload);
+ }
+ req.end();
+ });
+ }
+
+ RED.nodes.registerType("http request",HTTPRequest,{
+ credentials: {
+ user: {type:"text"},
+ password: {type: "password"}
+ }
+ });
+}
diff --git a/dgbuilder/core_nodes/io/22-websocket.html b/dgbuilder/core_nodes/io/22-websocket.html
new file mode 100644
index 00000000..ff6ed742
--- /dev/null
+++ b/dgbuilder/core_nodes/io/22-websocket.html
@@ -0,0 +1,163 @@
+<!--
+ 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.
+-->
+
+<!-- WebSocket Input Node -->
+<script type="text/x-red" data-template-name="websocket in">
+ <div class="form-row">
+ <label for="node-input-server"><i class="fa fa-bookmark"></i> Path</label>
+ <input type="text" id="node-input-server">
+ </div>
+ <div class="form-row">
+ <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
+ <input type="text" id="node-input-name" placeholder="Name">
+ </div>
+</script>
+
+<script type="text/x-red" data-help-name="websocket in">
+ <p>WebSocket input node.</p>
+ <p>By default, the data received from the WebSocket will be in <b>msg.payload</b>.
+ The listener can be configured to expect a properly formed JSON string, in which
+ case it will parse the JSON and send on the resulting object as the entire message.</p>
+</script>
+
+<script type="text/javascript">
+ RED.nodes.registerType('websocket in',{
+ category: 'input',
+ defaults: {
+ name: {value:""},
+ server: {type:"websocket-listener"}
+ },
+ color:"rgb(215, 215, 160)",
+ inputs:0,
+ outputs:1,
+ icon: "white-globe.png",
+ label: function() {
+ var wsNode = RED.nodes.node(this.server);
+ return this.name||(wsNode?"[ws] "+wsNode.label():"websocket");
+ },
+ labelStyle: function() {
+ return this.name?"node_label_italic":"";
+ }
+ });
+</script>
+
+<!-- WebSocket out Node -->
+<script type="text/x-red" data-template-name="websocket out">
+ <div class="form-row">
+ <label for="node-input-server"><i class="fa fa-bookmark"></i> Path</label>
+ <input type="text" id="node-input-server">
+ </div>
+ <div class="form-row">
+ <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
+ <input type="text" id="node-input-name" placeholder="Name">
+ </div>
+</script>
+
+<script type="text/x-red" data-help-name="websocket out">
+ <p>WebSocket out node.</p>
+ <p>By default, <b>msg.payload</b> will be sent over the WebSocket. The listener
+ can be configured to encode the entire message object as a JSON string and send that
+ over the WebSocket.</p>
+
+ <p>If the message arriving at this node started at a WebSocket In node, the message
+ will be sent back to the client that triggered the flow. Otherwise, the message
+ will be broadcast to all connected clients.</p>
+ <p>If you want to broadcast a message that started at a WebSocket In node, you
+ should delete the <b>msg._session</b> property within the flow</p>.
+</script>
+
+<script type="text/javascript">
+ RED.nodes.registerType('websocket out',{
+ category: 'output',
+ defaults: {
+ name: {value:""},
+ server: {type:"websocket-listener", required:true}
+ },
+ color:"rgb(215, 215, 160)",
+ inputs:1,
+ outputs:0,
+ icon: "white-globe.png",
+ align: "right",
+ label: function() {
+ var wsNode = RED.nodes.node(this.server);
+ return this.name||(wsNode?"[ws] "+wsNode.label():"websocket");
+ },
+ labelStyle: function() {
+ return this.name?"node_label_italic":"";
+ }
+ });
+</script>
+
+<!-- WebSocket Server configuration node -->
+<script type="text/x-red" data-template-name="websocket-listener">
+ <div class="form-row">
+ <label for="node-config-input-path"><i class="fa fa-bookmark"></i> Path</label>
+ <input type="text" id="node-config-input-path" placeholder="/ws/example">
+ </div>
+ <div class="form-row">
+ <label for="node-config-input-wholemsg">&nbsp;</label>
+ <select type="text" id="node-config-input-wholemsg" style="width: 70%;">
+ <option value="false">Send/Receive payload</option>
+ <option value="true">Send/Receive entire message</option>
+ </select>
+ </div>
+ <div class="form-tips">
+ Be default, <code>payload</code> will contain the data to be sent over, or received from a websocket.
+ The listener can be configured to send or receive the entire message object as a JSON formatted string.
+ <p id="node-config-ws-tip">This path will be relative to <code><span id="node-config-ws-path"></span></code>.</p>
+ </div>
+</script>
+
+<script type="text/x-red" data-help-name="websocket-listener">
+ <p>This configuration node creates a WebSocket Server using the specified path</p>
+</script>
+
+<script type="text/javascript">
+ RED.nodes.registerType('websocket-listener',{
+ category: 'config',
+ defaults: {
+ path: {value:"",required:true,validate:RED.validators.regex(/^((?!\/debug\/ws).)*$/) },
+ wholemsg: {value:"false"}
+ },
+ inputs:0,
+ outputs:0,
+ label: function() {
+ var root = RED.settings.httpNodeRoot;
+ if (root.slice(-1) != "/") {
+ root = root+"/";
+ }
+ if (this.path.charAt(0) == "/") {
+ root += this.path.slice(1);
+ } else {
+ root += this.path;
+ }
+ return root;
+ },
+ oneditprepare: function() {
+ var root = RED.settings.httpNodeRoot;
+ if (root.slice(-1) == "/") {
+ root = root.slice(0,-1);
+ }
+ if (root == "") {
+ $("#node-config-ws-tip").hide();
+ } else {
+ $("#node-config-ws-path").html(root);
+ $("#node-config-ws-tip").show();
+ }
+ //document.getElementById("node-config-wsdocpath").innerHTML=
+ }
+ });
+</script>
diff --git a/dgbuilder/core_nodes/io/22-websocket.js b/dgbuilder/core_nodes/io/22-websocket.js
new file mode 100644
index 00000000..72eda502
--- /dev/null
+++ b/dgbuilder/core_nodes/io/22-websocket.js
@@ -0,0 +1,185 @@
+/**
+ * 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.
+ **/
+
+module.exports = function(RED) {
+ "use strict";
+ var ws = require("ws"),
+ inspect = require("sys").inspect;
+
+ // A node red node that sets up a local websocket server
+ function WebSocketListenerNode(n) {
+ // Create a RED node
+ RED.nodes.createNode(this,n);
+
+ var node = this;
+
+ // Store local copies of the node configuration (as defined in the .html)
+ node.path = n.path;
+ node.wholemsg = (n.wholemsg === "true");
+
+ node._inputNodes = []; // collection of nodes that want to receive events
+
+ var path = RED.settings.httpNodeRoot || "/";
+ path = path + (path.slice(-1) == "/" ? "":"/") + (node.path.charAt(0) == "/" ? node.path.substring(1) : node.path);
+
+ // Workaround https://github.com/einaros/ws/pull/253
+ // Listen for 'newListener' events from RED.server
+ node._serverListeners = {};
+
+ var storeListener = function(/*String*/event,/*function*/listener){
+ if(event == "error" || event == "upgrade" || event == "listening"){
+ node._serverListeners[event] = listener;
+ }
+ }
+
+ node._clients = {};
+
+ RED.server.addListener('newListener',storeListener);
+
+ // Create a WebSocket Server
+ node.server = new ws.Server({server:RED.server,path:path});
+
+ // Workaround https://github.com/einaros/ws/pull/253
+ // Stop listening for new listener events
+ RED.server.removeListener('newListener',storeListener);
+
+ node.server.on('connection', function(socket){
+ var id = (1+Math.random()*4294967295).toString(16);
+ node._clients[id] = socket;
+ socket.on('close',function() {
+ delete node._clients[id];
+ });
+ socket.on('message',function(data,flags){
+ node.handleEvent(id,socket,'message',data,flags);
+ });
+ socket.on('error', function(err) {
+ node.warn("An error occured on the ws connection: "+inspect(err));
+ });
+ });
+
+ node.on("close", function() {
+ // Workaround https://github.com/einaros/ws/pull/253
+ // Remove listeners from RED.server
+ var listener = null;
+ for(var event in node._serverListeners) {
+ if (node._serverListeners.hasOwnProperty(event)) {
+ listener = node._serverListeners[event];
+ if(typeof listener === "function"){
+ RED.server.removeListener(event,listener);
+ }
+ }
+ }
+ node._serverListeners = {};
+ node.server.close();
+ node._inputNodes = [];
+ });
+ }
+ RED.nodes.registerType("websocket-listener",WebSocketListenerNode);
+
+ WebSocketListenerNode.prototype.registerInputNode = function(/*Node*/handler){
+ this._inputNodes.push(handler);
+ }
+
+ WebSocketListenerNode.prototype.handleEvent = function(id,/*socket*/socket,/*String*/event,/*Object*/data,/*Object*/flags){
+ var msg;
+ if (this.wholemsg) {
+ try {
+ msg = JSON.parse(data);
+ }
+ catch(err) {
+ msg = { payload:data };
+ }
+ } else {
+ msg = {
+ payload:data
+ };
+ }
+ msg._session = {type:"websocket",id:id};
+
+ for (var i = 0; i < this._inputNodes.length; i++) {
+ this._inputNodes[i].send(msg);
+ }
+ }
+
+ WebSocketListenerNode.prototype.broadcast = function(data){
+ try {
+ for (var i = 0; i < this.server.clients.length; i++) {
+ this.server.clients[i].send(data);
+ }
+ }
+ catch(e) { // swallow any errors
+ this.warn("ws:"+i+" : "+e);
+ }
+ }
+
+ WebSocketListenerNode.prototype.send = function(id,data) {
+ var session = this._clients[id];
+ if (session) {
+ try {
+ session.send(data);
+ }
+ catch(e) { // swallow any errors
+ }
+ }
+ }
+
+ function WebSocketInNode(n) {
+ RED.nodes.createNode(this,n);
+ this.server = n.server;
+ var node = this;
+ this.serverConfig = RED.nodes.getNode(this.server);
+ if (this.serverConfig) {
+ this.serverConfig.registerInputNode(this);
+ } else {
+ this.error("Missing server configuration");
+ }
+ }
+ RED.nodes.registerType("websocket in",WebSocketInNode);
+
+ function WebSocketOutNode(n) {
+ RED.nodes.createNode(this,n);
+ var node = this;
+ this.server = n.server;
+ this.serverConfig = RED.nodes.getNode(this.server);
+ if (!this.serverConfig) {
+ this.error("Missing server configuration");
+ }
+ this.on("input", function(msg) {
+ var payload;
+ if (this.serverConfig.wholemsg) {
+ delete msg._session;
+ payload = JSON.stringify(msg);
+ } else {
+ if (!Buffer.isBuffer(msg.payload)) { // if it's not a buffer make sure it's a string.
+ payload = RED.util.ensureString(msg.payload);
+ }
+ else {
+ payload = msg.payload;
+ }
+ }
+ if (msg._session && msg._session.type == "websocket") {
+ node.serverConfig.send(msg._session.id,payload);
+ } else {
+ node.serverConfig.broadcast(payload,function(error){
+ if (!!error) {
+ node.warn("An error occurred while sending:" + inspect(error));
+ }
+ });
+ }
+ });
+ }
+ RED.nodes.registerType("websocket out",WebSocketOutNode);
+}
diff --git a/dgbuilder/core_nodes/io/23-watch.html b/dgbuilder/core_nodes/io/23-watch.html
new file mode 100644
index 00000000..8bf22be5
--- /dev/null
+++ b/dgbuilder/core_nodes/io/23-watch.html
@@ -0,0 +1,57 @@
+<!--
+ 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="watch">
+ <div class="form-row node-input-filename">
+ <label for="node-input-files"><i class="fa fa-file"></i> File(s)</label>
+ <input type="text" id="node-input-files" placeholder="File(s) or Directory">
+ </div>
+ <div class="form-row">
+ <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
+ <input type="text" id="node-input-name" placeholder="Name">
+ </div>
+ <div id="node-input-tip" class="form-tips">On Windows you must use double slashes \\ in any directory names.</div>
+</script>
+
+<script type="text/x-red" data-help-name="watch">
+ <p>Watches a directory or file for any changes.</p>
+ <p>You can enter a list of comma separated directories or files if you like. You will need to put " around any that have spaces in.</p>
+ <p>On Windows you must use double slashes \\ in any directory names.</p>
+ <p>The full filename of the file that actually changed is put into <b>msg.payload</b>, while a stringified version of the watched criteria is returned in <b>msg.topic</b>.</p>
+ <p><b>msg.file</b> contains just the short filename of the file that changed.</p>
+ <p>Of course in Linux, <i>everything</i> could be a file and thus watched...</p>
+ <p><b>Note: </b>The directory or file must exist in order to be watched. If the file or directory gets deleted it may no longer be monitored even if it gets re-created.</p>
+</script>
+
+<script type="text/javascript">
+ RED.nodes.registerType('watch',{
+ category: 'advanced-input',
+ defaults: {
+ name: {value:""},
+ files: {value:"",required:true}
+ },
+ color:"BurlyWood",
+ inputs:0,
+ outputs:1,
+ icon: "watch.png",
+ label: function() {
+ return this.name||this.files;
+ },
+ labelStyle: function() {
+ return this.name?"node_label_italic":"";
+ }
+ });
+</script>
diff --git a/dgbuilder/core_nodes/io/23-watch.js b/dgbuilder/core_nodes/io/23-watch.js
new file mode 100644
index 00000000..8a17f5ac
--- /dev/null
+++ b/dgbuilder/core_nodes/io/23-watch.js
@@ -0,0 +1,51 @@
+/**
+ * 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.
+ **/
+
+module.exports = function(RED) {
+ "use strict";
+ var Notify = require("fs.notify");
+ var fs = require("fs");
+ var sep = require("path").sep;
+
+ function WatchNode(n) {
+ RED.nodes.createNode(this,n);
+
+ this.files = n.files.split(",");
+ for (var f =0; f < this.files.length; f++) {
+ this.files[f] = this.files[f].trim();
+ }
+ this.p = (this.files.length == 1) ? this.files[0] : JSON.stringify(this.files);
+ var node = this;
+
+ var notifications = new Notify(node.files);
+ notifications.on('change', function (file, event, path) {
+ try {
+ if (fs.statSync(path).isDirectory()) { path = path + sep + file; }
+ } catch(e) { }
+ var msg = { payload: path, topic: node.p, file: file };
+ node.send(msg);
+ });
+
+ notifications.on('error', function (error, path) {
+ node.warn(error);
+ });
+
+ this.close = function() {
+ notifications.close();
+ }
+ }
+ RED.nodes.registerType("watch",WatchNode);
+}
diff --git a/dgbuilder/core_nodes/io/25-serial.html b/dgbuilder/core_nodes/io/25-serial.html
new file mode 100644
index 00000000..225e4dc3
--- /dev/null
+++ b/dgbuilder/core_nodes/io/25-serial.html
@@ -0,0 +1,265 @@
+<!--
+ Copyright 2013,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="serial in">
+ <div class="form-row node-input-serial">
+ <label for="node-input-serial"><i class="fa fa-random"></i> Serial Port</label>
+ <input type="text" id="node-input-serial">
+ </div>
+ <div class="form-row">
+ <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
+ <input type="text" id="node-input-name" placeholder="Name">
+ </div>
+</script>
+
+<script type="text/x-red" data-help-name="serial in">
+ <p>Reads data from a local serial port.</p>
+ <p>Can either <ul><li>wait for a "split" character (default \n). Also accepts hex notation (0x0a).</li>
+ <li>Wait for a timeout in milliseconds for the first character received</li>
+ <li>Wait to fill a fixed sized buffer</li></ul></p>
+ <p>It then outputs <b>msg.payload</b> as either a UTF8 ascii string or a binary Buffer object.</p>
+ <p>If no split character is specified, or a timeout or buffer size of 0, then a stream of single characters is sent - again either as ascii chars or size 1 binary buffers.</p>
+</script>
+
+<script type="text/javascript">
+ RED.nodes.registerType('serial in',{
+ category: 'input',
+ defaults: {
+ name: {name:""},
+ serial: {type:"serial-port",required:true}
+ },
+ color:"BurlyWood",
+ inputs:0,
+ outputs:1,
+ icon: "serial.png",
+ label: function() {
+ var serialNode = RED.nodes.node(this.serial);
+ return this.name||(serialNode?serialNode.label().split(":")[0]:"serial");
+ },
+ labelStyle: function() {
+ return this.name?"node_label_italic":"";
+ }
+ });
+</script>
+
+<script type="text/x-red" data-template-name="serial out">
+ <div class="form-row node-input-serial">
+ <label for="node-input-serial"><i class="fa fa-random"></i> Serial Port</label>
+ <input type="text" id="node-input-serial">
+ </div>
+ <div class="form-row">
+ <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
+ <input type="text" id="node-input-name" placeholder="Name">
+ </div>
+</script>
+
+<script type="text/x-red" data-help-name="serial out">
+ <p>Provides a connection to an outbound serial port.</p>
+ <p>Only the <b>msg.payload</b> is sent.</p>
+ <p>Optionally the new line character used to split the input can be appended to every message sent out to the serial port.</p>
+</script>
+
+<script type="text/javascript">
+ RED.nodes.registerType('serial out',{
+ category: 'output',
+ defaults: {
+ name: {name:""},
+ serial: {type:"serial-port",required:true}
+ },
+ color:"BurlyWood",
+ inputs:1,
+ outputs:0,
+ icon: "serial.png",
+ align: "right",
+ label: function() {
+ var serialNode = RED.nodes.node(this.serial);
+ return this.name||(serialNode?serialNode.label().split(":")[0]:"serial");
+ },
+ labelStyle: function() {
+ return this.name?"node_label_italic":"";
+ }
+ });
+</script>
+
+
+<script type="text/x-red" data-template-name="serial-port">
+ <div class="form-row">
+ <label for="node-config-input-serialport"><i class="fa fa-random"></i> Serial Port</label>
+ <input type="text" id="node-config-input-serialport" style="width:60%;" placeholder="/dev/ttyUSB0"/>
+ <a id="node-config-lookup-serial" class="btn"><i id="node-config-lookup-serial-icon" class="fa fa-search"></i></a>
+ </div>
+ <div class="form-row">
+ <table><tr>
+ <td width = "102px"><i class="fa fa-wrench"></i> Settings</td>
+ <td width = "100px">Baud Rate</td>
+ <td width = "80px">Data Bits</td>
+ <td width = "80px">Parity</td>
+ <td width = "80px">Stop Bits</td>
+ </tr><tr><td>&nbsp;</td>
+ <td>
+ <select type="text" id="node-config-input-serialbaud" style="width: 100px;">
+ <option value="115200">115200</option>
+ <option value="57600">57600</option>
+ <option value="38400">38400</option>
+ <option value="19200">19200</option>
+ <option value="9600">9600</option>
+ <option value="4800">4800</option>
+ <option value="2400">2400</option>
+ <option value="1800">1800</option>
+ <option value="1200">1200</option>
+ <option value="600">600</option>
+ <option value="300">300</option>
+ <option value="200">200</option>
+ <option value="150">150</option>
+ <option value="134">134</option>
+ <option value="110">110</option>
+ <option value="75">75</option>
+ <option value="50">50</option>
+ </select>
+ </td><td>
+ <select type="text" id="node-config-input-databits" style="width: 80px;">
+ <option value="8">8</option>
+ <option value="7">7</option>
+ <option value="6">6</option>
+ <option value="5">5</option>
+ </select>
+ </td><td>
+ <select type="text" id="node-config-input-parity" style="width: 80px;">
+ <option value="none">None</option>
+ <option value="even">Even</option>
+ <option value="mark">Mark</option>
+ <option value="odd">Odd</option>
+ <option value="space">Space</option>
+ </select>
+ </td><td>
+ <select type="text" id="node-config-input-stopbits" style="width: 80px;">
+ <option value="2">2</option>
+ <option value="1">1</option>
+ </select>
+ </td>
+ </tr></table><br/>
+
+ <div class="form-row">
+ <label for="node-config-input-out"><i class="fa fa-cut"></i> Split input</label>
+ <select type="text" id="node-config-input-out" style="width:52%;">
+ <option value="char">when character received is</option>
+ <option value="time">after a fixed timeout of</option>
+ <option value="count">a fixed number of characters</option>
+ </select>
+ <input type="text" id="node-config-input-newline" style="width:50px;">
+ <span id="node-units"></span>
+ </div>
+
+ <div class="form-row">
+ <label for="node-config-input-bin"><i class="fa fa-sign-in"></i> and deliver</label>
+ <select type="text" id="node-config-input-bin" style="width: 77%;">
+ <option value="false">ascii strings</option>
+ <option value="bin">binary buffers</option>
+ </select>
+ </div>
+ <br/>
+ <div class="form-row" id="node-config-addchar">
+ <label for="node-config-input-addchar"><i class="fa fa-sign-out"></i> On output</label>
+ <select type="text" id="node-config-input-addchar" style="width: 77%;">
+ <option value="false">don't add 'split' character to output messages</option>
+ <option value="true">add 'split' character to output messages</option>
+ </select>
+ </div>
+ <div class="form-tips" id="tip-split">Tip: the "Split on" character is used to split the input into separate messages. It can also be added to every message sent out to the serial port.</div>
+ <div class="form-tips" id="tip-bin" hidden>Tip: In timeout mode timeout starts from arrival of first character.</div>
+ <script>
+ var previous = null;
+ $("#node-config-input-out").on('focus', function () { previous = this.value; }).change(function() {
+ if (previous == null) { previous = $("#node-config-input-out").val(); }
+ if ($("#node-config-input-out").val() == "char") {
+ if (previous != "char") { $("#node-config-input-newline").val("\\n"); }
+ $("#node-units").text("");
+ $("#node-config-addchar").show();
+ $("#tip-split").show();
+ $("#tip-bin").hide();
+ }
+ else if ($("#node-config-input-out").val() == "time") {
+ if (previous != "time") { $("#node-config-input-newline").val("0"); }
+ $("#node-units").text("ms");
+ $("#node-config-addchar").hide();
+ $("#node-config-input-addchar").val("false");
+ $("#tip-split").hide();
+ $("#tip-bin").show();
+ }
+ else {
+ if (previous != "count") { $("#node-config-input-newline").val("12"); }
+ $("#node-units").text("chars");
+ $("#node-config-addchar").hide();
+ $("#node-config-input-addchar").val("false");
+ $("#tip-split").hide();
+ $("#tip-bin").hide();
+ }
+ });
+
+ </script>
+</script>
+
+<script type="text/javascript">
+ RED.nodes.registerType('serial-port',{
+ category: 'config',
+ defaults: {
+ //name: {value:""},
+ serialport: {value:"",required:true},
+ serialbaud: {value:57600,required:true},
+ databits: {value:8,required:true},
+ parity: {value:"none",required:true},
+ stopbits: {value:1,required:true},
+ newline: {value:"\\n"},
+ bin: {value:""},
+ out: {value:""},
+ addchar: {value:false}
+ },
+ label: function() {
+ this.serialbaud = this.serialbaud || 57600;
+ this.databits = this.databits || 8;
+ this.parity = this.parity || 'none';
+ this.stopbits = this.stopbits || 1;
+ return this.serialport+":"+this.serialbaud+"-"+this.databits+this.parity.charAt(0).toUpperCase()+this.stopbits;
+ },
+ oneditprepare: function() {
+ try {
+ $("#node-config-input-serialport").autocomplete( "destroy" );
+ } catch(err) {
+ }
+ $("#node-config-lookup-serial").click(function() {
+ //$("#node-config-lookup-serial-icon").removeClass('fa fa-search');
+ //$("#node-config-lookup-serial-icon").addClass('fa fa-spinner');
+ $("#node-config-lookup-serial").addClass('disabled');
+ $.getJSON('serialports',function(data) {
+ //$("#node-config-lookup-serial-icon").addClass('fa fa-search');
+ //$("#node-config-lookup-serial-icon").removeClass('fa fa-spinner');
+ $("#node-config-lookup-serial").removeClass('disabled');
+ var ports = [];
+ $.each(data, function(i, port){
+ ports.push(port.comName);
+ });
+ $("#node-config-input-serialport").autocomplete({
+ source:ports,
+ minLength:0,
+ close: function( event, ui ) {
+ $("#node-config-input-serialport").autocomplete( "destroy" );
+ }
+ }).autocomplete("search","");
+ });
+ });
+ }
+ });
+</script>
diff --git a/dgbuilder/core_nodes/io/25-serial.js b/dgbuilder/core_nodes/io/25-serial.js
new file mode 100644
index 00000000..96e4aca6
--- /dev/null
+++ b/dgbuilder/core_nodes/io/25-serial.js
@@ -0,0 +1,310 @@
+/**
+* Copyright 2013,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.
+**/
+
+module.exports = function(RED) {
+ "use strict";
+ var settings = RED.settings;
+ var events = require("events");
+ var util = require("util");
+ var serialp = require("serialport");
+ var bufMaxSize = 32768; // Max serial buffer size, for inputs...
+
+ // TODO: 'serialPool' should be encapsulated in SerialPortNode
+
+ function SerialPortNode(n) {
+ RED.nodes.createNode(this,n);
+ this.serialport = n.serialport;
+ this.newline = n.newline;
+ this.addchar = n.addchar || "false";
+ this.serialbaud = parseInt(n.serialbaud) || 57600;
+ this.databits = parseInt(n.databits) || 8;
+ this.parity = n.parity || "none";
+ this.stopbits = parseInt(n.stopbits) || 1;
+ this.bin = n.bin || "false";
+ this.out = n.out || "char";
+ }
+ RED.nodes.registerType("serial-port",SerialPortNode);
+
+ function SerialOutNode(n) {
+ RED.nodes.createNode(this,n);
+ this.serial = n.serial;
+ this.serialConfig = RED.nodes.getNode(this.serial);
+
+ if (this.serialConfig) {
+ var node = this;
+ node.port = serialPool.get(this.serialConfig.serialport,
+ this.serialConfig.serialbaud,
+ this.serialConfig.databits,
+ this.serialConfig.parity,
+ this.serialConfig.stopbits,
+ this.serialConfig.newline);
+ node.addCh = "";
+ if (node.serialConfig.addchar == "true") {
+ node.addCh = this.serialConfig.newline.replace("\\n","\n").replace("\\r","\r").replace("\\t","\t").replace("\\e","\e").replace("\\f","\f").replace("\\0","\0");
+ }
+ node.on("input",function(msg) {
+ var payload = msg.payload;
+ if (!Buffer.isBuffer(payload)) {
+ if (typeof payload === "object") {
+ payload = JSON.stringify(payload);
+ } else {
+ payload = payload.toString();
+ }
+ payload += node.addCh;
+ } else if (node.addCh !== "") {
+ payload = Buffer.concat([payload,new Buffer(node.addCh)]);
+ }
+ node.port.write(payload,function(err,res) {
+ if (err) {
+ node.error(err);
+ }
+ });
+ });
+ node.port.on('ready', function() {
+ node.status({fill:"green",shape:"dot",text:"connected"});
+ });
+ node.port.on('closed', function() {
+ node.status({fill:"red",shape:"ring",text:"not connected"});
+ });
+ } else {
+ this.error("missing serial config");
+ }
+
+ this.on("close", function(done) {
+ if (this.serialConfig) {
+ serialPool.close(this.serialConfig.serialport,done);
+ } else {
+ done();
+ }
+ });
+ }
+ RED.nodes.registerType("serial out",SerialOutNode);
+
+
+ function SerialInNode(n) {
+ RED.nodes.createNode(this,n);
+ this.serial = n.serial;
+ this.serialConfig = RED.nodes.getNode(this.serial);
+
+ if (this.serialConfig) {
+ var node = this;
+ node.tout = null;
+ var buf;
+ if (node.serialConfig.out != "count") { buf = new Buffer(bufMaxSize); }
+ else { buf = new Buffer(Number(node.serialConfig.newline)); }
+ var i = 0;
+ node.status({fill:"grey",shape:"dot",text:"unknown"});
+ node.port = serialPool.get(this.serialConfig.serialport,
+ this.serialConfig.serialbaud,
+ this.serialConfig.databits,
+ this.serialConfig.parity,
+ this.serialConfig.stopbits,
+ this.serialConfig.newline
+ );
+
+ var splitc;
+ if (node.serialConfig.newline.substr(0,2) == "0x") {
+ splitc = new Buffer([parseInt(node.serialConfig.newline)]);
+ } else {
+ splitc = new Buffer(node.serialConfig.newline.replace("\\n","\n").replace("\\r","\r").replace("\\t","\t").replace("\\e","\e").replace("\\f","\f").replace("\\0","\0"));
+ }
+
+ this.port.on('data', function(msg) {
+ // single char buffer
+ if ((node.serialConfig.newline === 0)||(node.serialConfig.newline === "")) {
+ if (node.serialConfig.bin !== "bin") { node.send({"payload": String.fromCharCode(msg)}); }
+ else { node.send({"payload": new Buffer([msg])}); }
+ }
+ else {
+ // do the timer thing
+ if (node.serialConfig.out === "time") {
+ if (node.tout) {
+ i += 1;
+ buf[i] = msg;
+ }
+ else {
+ node.tout = setTimeout(function () {
+ node.tout = null;
+ var m = new Buffer(i+1);
+ buf.copy(m,0,0,i+1);
+ if (node.serialConfig.bin !== "bin") { m = m.toString(); }
+ node.send({"payload": m});
+ m = null;
+ }, node.serialConfig.newline);
+ i = 0;
+ buf[0] = msg;
+ }
+ }
+ // count bytes into a buffer...
+ else if (node.serialConfig.out === "count") {
+ buf[i] = msg;
+ i += 1;
+ if ( i >= parseInt(node.serialConfig.newline)) {
+ var m = new Buffer(i);
+ buf.copy(m,0,0,i);
+ if (node.serialConfig.bin !== "bin") { m = m.toString(); }
+ node.send({"payload":m});
+ m = null;
+ i = 0;
+ }
+ }
+ // look to match char...
+ else if (node.serialConfig.out === "char") {
+ buf[i] = msg;
+ i += 1;
+ if ((msg === splitc[0]) || (i === bufMaxSize)) {
+ var m = new Buffer(i);
+ buf.copy(m,0,0,i);
+ if (node.serialConfig.bin !== "bin") { m = m.toString(); }
+ node.send({"payload":m});
+ m = null;
+ i = 0;
+ }
+ }
+ else { console.log("Should never get here"); }
+ }
+ });
+ this.port.on('ready', function() {
+ node.status({fill:"green",shape:"dot",text:"connected"});
+ });
+ this.port.on('closed', function() {
+ node.status({fill:"red",shape:"ring",text:"not connected"});
+ });
+ } else {
+ this.error("missing serial config");
+ }
+
+ this.on("close", function(done) {
+ if (this.serialConfig) {
+ serialPool.close(this.serialConfig.serialport,done);
+ } else {
+ done();
+ }
+ });
+ }
+ RED.nodes.registerType("serial in",SerialInNode);
+
+
+ var serialPool = function() {
+ var connections = {};
+ return {
+ get:function(port,baud,databits,parity,stopbits,newline,callback) {
+ var id = port;
+ if (!connections[id]) {
+ connections[id] = function() {
+ var obj = {
+ _emitter: new events.EventEmitter(),
+ serial: null,
+ _closing: false,
+ tout: null,
+ on: function(a,b) { this._emitter.on(a,b); },
+ close: function(cb) { this.serial.close(cb); },
+ write: function(m,cb) { this.serial.write(m,cb); },
+ }
+ //newline = newline.replace("\\n","\n").replace("\\r","\r");
+ var setupSerial = function() {
+ //if (newline == "") {
+ obj.serial = new serialp.SerialPort(port,{
+ baudrate: baud,
+ databits: databits,
+ parity: parity,
+ stopbits: stopbits,
+ parser: serialp.parsers.raw
+ },true, function(err, results) { if (err) { obj.serial.emit('error',err); } });
+ //}
+ //else {
+ // obj.serial = new serialp.SerialPort(port,{
+ // baudrate: baud,
+ // databits: databits,
+ // parity: parity,
+ // stopbits: stopbits,
+ // parser: serialp.parsers.readline(newline)
+ // },true, function(err, results) { if (err) obj.serial.emit('error',err); });
+ //}
+ obj.serial.on('error', function(err) {
+ util.log("[serial] serial port "+port+" error "+err);
+ obj._emitter.emit('closed');
+ obj.tout = setTimeout(function() {
+ setupSerial();
+ }, settings.serialReconnectTime);
+ });
+ obj.serial.on('close', function() {
+ if (!obj._closing) {
+ util.log("[serial] serial port "+port+" closed unexpectedly");
+ obj._emitter.emit('closed');
+ obj.tout = setTimeout(function() {
+ setupSerial();
+ }, settings.serialReconnectTime);
+ }
+ });
+ obj.serial.on('open',function() {
+ util.log("[serial] serial port "+port+" opened at "+baud+" baud "+databits+""+parity.charAt(0).toUpperCase()+stopbits);
+ if (obj.tout) { clearTimeout(obj.tout); }
+ //obj.serial.flush();
+ obj._emitter.emit('ready');
+ });
+ obj.serial.on('data',function(d) {
+ //console.log(Buffer.isBuffer(d),d.length,d);
+ //if (typeof d !== "string") {
+ // //d = d.toString();
+ for (var z=0; z<d.length; z++) {
+ obj._emitter.emit('data',d[z]);
+ }
+ //}
+ //else {
+ // obj._emitter.emit('data',d);
+ //}
+ });
+ obj.serial.on("disconnect",function() {
+ util.log("[serial] serial port "+port+" gone away");
+ });
+ }
+ setupSerial();
+ return obj;
+ }();
+ }
+ return connections[id];
+ },
+ close: function(port,done) {
+ if (connections[port]) {
+ if (connections[port].tout != null) {
+ clearTimeout(connections[port].tout);
+ }
+ connections[port]._closing = true;
+ try {
+ connections[port].close(function() {
+ util.log("[serial] serial port closed");
+ done();
+ });
+ }
+ catch(err) { }
+ delete connections[port];
+ } else {
+ done();
+ }
+ }
+ }
+ }();
+
+ RED.httpAdmin.get("/serialports",function(req,res) {
+ serialp.list(function (err, ports) {
+ //console.log(JSON.stringify(ports));
+ res.writeHead(200, {'Content-Type': 'text/plain'});
+ res.write(JSON.stringify(ports));
+ res.end();
+ });
+ });
+}
diff --git a/dgbuilder/core_nodes/io/31-tcpin.html b/dgbuilder/core_nodes/io/31-tcpin.html
new file mode 100644
index 00000000..c8cec599
--- /dev/null
+++ b/dgbuilder/core_nodes/io/31-tcpin.html
@@ -0,0 +1,299 @@
+<!--
+ 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="tcp in">
+ <div class="form-row">
+ <label for="node-input-server"><i class="fa fa-dot-circle-o"></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: 60%;">
+ </div>
+
+ <div class="form-row">
+ <label><i class="fa fa-sign-out"></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 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="fa fa-tasks"></i> Topic</label>
+ <input type="text" id="node-input-topic" placeholder="Topic">
+ </div>
+ <div class="form-row">
+ <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
+ <input type="text" id="node-input-name" placeholder="Name">
+ </div>
+</script>
+
+<script type="text/x-red" data-help-name="tcp in">
+ <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">
+ RED.nodes.registerType('tcp in',{
+ category: 'input',
+ color:"Silver",
+ defaults: {
+ 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()},
+ datamode:{value:"stream"},
+ datatype:{value:"buffer"},
+ newline:{value:""},
+ topic: {value:""},
+ name: {value:""},
+ base64: {/*deprecated*/ value:false,required:true}
+ },
+ inputs:0,
+ outputs:1,
+ icon: "bridge-dash.png",
+ label: function() {
+ 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>
+
+
+<script type="text/x-red" data-template-name="tcp out">
+ <div class="form-row">
+ <label for="node-input-beserver"><i class="fa fa-dot-circle-o"></i> Type</label>
+ <select id="node-input-beserver" style="width:150px; margin-right:5px;">
+ <option value="server">Listen on</option>
+ <option value="client">Connect to</option>
+ <option value="reply">Reply to TCP</option>
+ </select>
+ <span id="node-input-port-row">port <input type="text" id="node-input-port" style="width: 50px"></span>
+ </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: 60%;">
+ </div>
+
+ <div class="form-row hidden" id="node-input-end-row">
+ <label>&nbsp;</label>
+ <input type="checkbox" id="node-input-end" style="display: inline-block; width: auto; vertical-align: top;">
+ <label for="node-input-end" style="width: 70%;">Close connection after each message is sent ?</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="fa fa-tag"></i> Name</label>
+ <input type="text" id="node-input-name" placeholder="Name">
+ </div>
+
+ <div class="form-tips hidden" id="fin-tip">
+ <b>Note:</b> Closing the connection after each message is generally not a good thing - but is useful to indicate an end-of-file for example.
+ </div>
+ <div class="form-tips hidden" id="fin-tip2">
+ <b>Note:</b> Closing the connection after each message is generally not a good thing - but is useful to indicate an end-of-file for example. The receiving client will need to reconnect.
+ </div>
+</script>
+
+<script type="text/x-red" data-help-name="tcp out">
+ <p>Provides a choice of tcp outputs. Can either connect to a remote tcp port,
+ accept incoming connections, or reply to messages received from a TCP In node.</p>
+ <p>Only <b>msg.payload</b> is sent.</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">
+ RED.nodes.registerType('tcp out',{
+ category: 'output',
+ color:"Silver",
+ defaults: {
+ host: {value:"",validate:function(v) { return (this.beserver != "client")||v.length > 0;} },
+ port: {value:"",validate:function(v) { return (this.beserver == "reply")||RED.validators.number()(v) } },
+ beserver: {value:"client",required:true},
+ base64: {value:false,required:true},
+ end: {value:false,required:true},
+ name: {value:""}
+ },
+ inputs:1,
+ outputs:0,
+ icon: "bridge-dash.png",
+ align: "right",
+ label: function() {
+ 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 == "reply") {
+ $("#node-input-port-row").hide();
+ $("#node-input-host-row").hide();
+ $("#node-input-end-row").hide();
+ } else {
+ $("#node-input-port-row").show();
+ $("#node-input-end-row").show();
+ }
+
+ if (sockettype == "client") {
+ $("#node-input-host-row").show();
+ $("#fin-tip").show();
+ } else {
+ $("#node-input-host-row").hide();
+ $("#fin-tip").hide();
+ }
+
+ if (sockettype == "server") {
+ $("#fin-tip2").show();
+ }
+ else {
+ $("#fin-tip2").hide();
+ }
+
+ };
+ updateOptions();
+ $("#node-input-beserver").change(updateOptions);
+ }
+ });
+</script>
+
+
+<script type="text/x-red" data-template-name="tcp request">
+ <div class="form-row">
+ <label for="node-input-server"><i class="fa fa-globe"></i> Server</label>
+ <input type="text" id="node-input-server" placeholder="ip.address" style="width:50%">
+ &nbsp;port <input type="text" id="node-input-port" style="width:50px">
+ </div>
+ <div class="form-row">
+ <label for="node-input-out"><i class="fa fa-sign-out"></i> Return</label>
+ <select type="text" id="node-input-out" style="width:52%;">
+ <option value="time">after a fixed timeout of</option>
+ <option value="char">when character received is</option>
+ <option value="count">a fixed number of characters</option>
+ <option value="sit">never. Keep connection open</option>
+ </select>
+ <input type="text" id="node-input-splitc" style="width:50px;">
+ <span id="node-units"></span>
+ </div>
+ <div class="form-row">
+ <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
+ <input type="text" id="node-input-name" placeholder="Name">
+ </div>
+ <div class="form-tips"><b>Tip:</b> outputs a binary <b>Buffer</b>, so you may want to .toString() it.</div>
+ <script>
+ var previous = null;
+ $("#node-input-out").on('focus', function () { previous = this.value; }).change(function() {
+ if (previous == null) { previous = $("#node-input-out").val(); }
+ if ($("#node-input-out").val() == "char") {
+ if (previous != "char") $("#node-input-splitc").val("\\n");
+ $("#node-units").text("");
+ }
+ else if ($("#node-input-out").val() == "time") {
+ if (previous != "time") $("#node-input-splitc").val("0");
+ $("#node-units").text("ms");
+ }
+ else if ($("#node-input-out").val() == "count") {
+ if (previous != "count") $("#node-input-splitc").val("12");
+ $("#node-units").text("chars");
+ }
+ else {
+ if (previous != "sit") $("#node-input-splitc").val("0");
+ $("#node-units").text("");
+ }
+ });
+</script>
+
+<script type="text/x-red" data-help-name="tcp request">
+ <p>A simple TCP request node - sends the <b>msg.payload</b> to a server tcp port and expects a response.</p>
+ <p>Connects, sends the "request", reads the "response". It can either count a number of
+ returned characters into a fixed buffer, match a specified character before returning,
+ wait a fixed timeout from first reply and then return, or just sit and wait for data.</p>
+ <p>The response will be output in <b>msg.payload</b> as a buffer, so you may want to .toString() it.</p>
+</script>
+
+<script type="text/javascript">
+ RED.nodes.registerType('tcp request',{
+ category: 'function',
+ color:"Silver",
+ defaults: {
+ server: {value:"",required:true},
+ port: {value:"",required:true,validate:RED.validators.number()},
+ out: {value:"time",required:true},
+ splitc: {value:"0",required:true},
+ name: {value:""}
+ },
+ inputs:1,
+ outputs:1,
+ icon: "bridge-dash.png",
+ label: function() {
+ return this.name || "tcp:"+(this.server?this.server+":":"")+this.port;
+ },
+ labelStyle: function() {
+ return this.name?"node_label_italic":"";
+ }
+ });
+</script>
diff --git a/dgbuilder/core_nodes/io/31-tcpin.js b/dgbuilder/core_nodes/io/31-tcpin.js
new file mode 100644
index 00000000..2e4e5e7b
--- /dev/null
+++ b/dgbuilder/core_nodes/io/31-tcpin.js
@@ -0,0 +1,472 @@
+/**
+ * Copyright 2013,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.
+ **/
+
+module.exports = function(RED) {
+ "use strict";
+ var reconnectTime = RED.settings.socketReconnectTime||10000;
+ var socketTimeout = RED.settings.socketTimeout||null;
+ var net = require('net');
+
+ var connectionPool = {};
+
+ function TcpIn(n) {
+ RED.nodes.createNode(this,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 = (typeof n.server == 'boolean')?n.server:(n.server == "server");
+ this.closing = false;
+ var node = this;
+ var count = 0;
+
+ if (!node.server) {
+ var buffer = null;
+ var client;
+ var reconnectTimeout;
+ var end = false;
+ var setupTcpClient = function() {
+ node.log("connecting to "+node.host+":"+node.port);
+ node.status({fill:"grey",shape:"dot",text:"connecting"});
+ var id = (1+Math.random()*4294967295).toString(16);
+ client = net.connect(node.port, node.host, function() {
+ buffer = (node.datatype == 'buffer')? new Buffer(0):"";
+ node.log("connected to "+node.host+":"+node.port);
+ node.status({fill:"green",shape:"dot",text:"connected"});
+ });
+ connectionPool[id] = client;
+
+ client.on('data', function (data) {
+ if (node.datatype != 'buffer') {
+ data = data.toString(node.datatype);
+ }
+ if (node.stream) {
+ 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) {
+ var msg = {topic:node.topic, payload:parts[i]};
+ msg._session = {type:"tcp",id:id};
+ node.send(msg);
+ }
+ buffer = parts[parts.length-1];
+ } else {
+ var msg = {topic:node.topic, payload:data};
+ msg._session = {type:"tcp",id:id};
+ 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() {
+ if (!node.stream || (node.datatype == "utf8" && node.newline != "" && buffer.length > 0)) {
+ var msg = {topic:node.topic,payload:buffer};
+ msg._session = {type:"tcp",id:id};
+ if (buffer.length !== 0) {
+ end = true; // only ask for fast re-connect if we actually got something
+ node.send(msg);
+ }
+ buffer = null;
+ }
+ });
+ client.on('close', function() {
+ delete connectionPool[id];
+ node.status({fill:"red",shape:"ring",text:"disconnected"});
+ if (!node.closing) {
+ if (end) { // if we were asked to close then try to reconnect once very quick.
+ end = false;
+ reconnectTimeout = setTimeout(setupTcpClient, 20);
+ }
+ else {
+ node.log("connection lost to "+node.host+":"+node.port);
+ reconnectTimeout = setTimeout(setupTcpClient, reconnectTime);
+ }
+ }
+ });
+ client.on('error', function(err) {
+ node.log(err);
+ });
+ }
+ setupTcpClient();
+
+ this.on('close', function() {
+ this.closing = true;
+ client.end();
+ clearTimeout(reconnectTimeout);
+ });
+ } else {
+ var server = net.createServer(function (socket) {
+ if (socketTimeout !== null) { socket.setTimeout(socketTimeout); }
+ var id = (1+Math.random()*4294967295).toString(16);
+ connectionPool[id] = socket;
+ node.status({text:++count+" connections"});
+
+ 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],ip:socket.remoteAddress,port:socket.remotePort};
+ msg._session = {type:"tcp",id:id};
+ node.send(msg);
+ }
+ buffer = parts[parts.length-1];
+ } else {
+ var msg = {topic:node.topic, payload:data};
+ msg._session = {type:"tcp",id:id};
+ 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 !== "")) {
+ if (buffer.length > 0) {
+ var msg = {topic:node.topic,payload:buffer};
+ msg._session = {type:"tcp",id:id};
+ node.send(msg);
+ }
+ buffer = null;
+ }
+ });
+ socket.on('timeout', function() {
+ node.log('timeout closed socket port '+node.port);
+ socket.end();
+ });
+ socket.on('close', function() {
+ delete connectionPool[id];
+ node.status({text:--count+" connections"});
+ });
+ socket.on('error',function(err) {
+ node.log(err);
+ });
+ });
+ server.on('error', function(err) {
+ if (err) {
+ node.error('unable to listen on port '+node.port+' : '+err);
+ }
+ });
+ server.listen(node.port, function(err) {
+ if (err) {
+ node.error('unable to listen on port '+node.port+' : '+err);
+ } else {
+ node.log('listening on port '+node.port);
+
+ node.on('close', function() {
+ node.closing = true;
+ server.close();
+ node.log('stopped listening on port '+node.port);
+ });
+ }
+ });
+ }
+
+ }
+ RED.nodes.registerType("tcp in",TcpIn);
+
+ function TcpOut(n) {
+ RED.nodes.createNode(this,n);
+ this.host = n.host;
+ this.port = n.port * 1;
+ this.base64 = n.base64;
+ this.doend = n.end || false;
+ this.beserver = n.beserver;
+ this.name = n.name;
+ this.closing = false;
+ var node = this;
+
+ if (!node.beserver||node.beserver=="client") {
+ var reconnectTimeout;
+ var client = null;
+ var connected = false;
+ var end = false;
+
+ var setupTcpClient = function() {
+ node.log("connecting to "+node.host+":"+node.port);
+ node.status({fill:"grey",shape:"dot",text:"connecting"});
+ client = net.connect(node.port, node.host, function() {
+ connected = true;
+ node.log("connected to "+node.host+":"+node.port);
+ node.status({fill:"green",shape:"dot",text:"connected"});
+ });
+ client.on('error', function (err) {
+ node.log('error : '+err);
+ });
+ client.on('end', function (err) {
+ });
+ client.on('close', function() {
+ node.status({fill:"red",shape:"ring",text:"disconnected"});
+ connected = false;
+ client.destroy();
+ if (!node.closing) {
+ if (end) {
+ end = false;
+ reconnectTimeout = setTimeout(setupTcpClient,20);
+ }
+ else {
+ node.log("connection lost to "+node.host+":"+node.port);
+ 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));
+ }
+ if (node.doend === true) {
+ end = true;
+ client.end();
+ }
+ }
+ });
+
+ node.on("close", function() {
+ this.closing = true;
+ client.end();
+ clearTimeout(reconnectTimeout);
+ });
+
+ } else if (node.beserver == "reply") {
+ node.on("input",function(msg) {
+ if (msg._session && msg._session.type == "tcp") {
+ var client = connectionPool[msg._session.id];
+ if (client) {
+ 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));
+ }
+ }
+ }
+ });
+ } else {
+ var connectedSockets = [];
+ node.status({text:"0 connections"});
+ var server = net.createServer(function (socket) {
+ if (socketTimeout !== null) { socket.setTimeout(socketTimeout); }
+ var remoteDetails = socket.remoteAddress+":"+socket.remotePort;
+ node.log("connection from "+remoteDetails);
+ connectedSockets.push(socket);
+ node.status({text:connectedSockets.length+" connections"});
+ socket.on('timeout', function() {
+ node.log('timeout closed socket port '+node.port);
+ socket.end();
+ });
+ socket.on('close',function() {
+ node.log("connection closed from "+remoteDetails);
+ connectedSockets.splice(connectedSockets.indexOf(socket),1);
+ node.status({text:connectedSockets.length+" connections"});
+ });
+ socket.on('error',function() {
+ node.log("socket error from "+remoteDetails);
+ connectedSockets.splice(connectedSockets.indexOf(socket),1);
+ node.status({text:connectedSockets.length+" connections"});
+ });
+ });
+
+ 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) {
+ if (node.doend === true) { connectedSockets[i].end(buffer); }
+ else { connectedSockets[i].write(buffer); }
+ }
+ }
+ });
+
+ server.on('error', function(err) {
+ if (err) {
+ node.error('unable to listen on port '+node.port+' : '+err);
+ }
+ });
+
+ server.listen(node.port, function(err) {
+ if (err) {
+ node.error('unable to listen on port '+node.port+' : '+err);
+ } else {
+ node.log('listening on port '+node.port);
+ node.on('close', function() {
+ server.close();
+ node.log('stopped listening on port '+node.port);
+ });
+ }
+ });
+ }
+ }
+ RED.nodes.registerType("tcp out",TcpOut);
+
+ function TcpGet(n) {
+ RED.nodes.createNode(this,n);
+ this.server = n.server;
+ this.port = Number(n.port);
+ this.out = n.out;
+ this.splitc = n.splitc;
+
+ if (this.out != "char") { this.splitc = Number(this.splitc); }
+ else { this.splitc.replace("\\n","\n").replace("\\r","\r").replace("\\t","\t").replace("\\e","\e").replace("\\f","\f").replace("\\0","\0"); }
+
+ var buf;
+ if (this.out == "count") { buf = new Buffer(this.splitc); }
+ else { buf = new Buffer(32768); } // set it to 32k... hopefully big enough for most.... but only hopefully
+
+ this.connected = false;
+ var node = this;
+ var client;
+
+ this.on("input", function(msg) {
+ var i = 0;
+ if ((!Buffer.isBuffer(msg.payload)) && (typeof msg.payload !== "string")) {
+ msg.payload = msg.payload.toString();
+ }
+ if (!node.connected) {
+ client = net.Socket();
+ client.setTimeout(socketTimeout);
+ node.status({});
+ client.connect(node.port, node.server, function() {
+ //node.log('client connected');
+ node.status({fill:"green",shape:"dot",text:"connected"});
+ node.connected = true;
+ client.write(msg.payload);
+ });
+
+ client.on('data', function(data) {
+ //node.log("data:"+ data.length+":"+ data);
+ if (node.splitc === 0) {
+ node.send({"payload": data});
+ }
+ else if (node.out === "sit") { // if we are staying connected just send the buffer
+ node.send({"payload": data});
+ }
+ else {
+ for (var j = 0; j < data.length; j++ ) {
+ if (node.out === "time") {
+ // do the timer thing
+ if (node.tout) {
+ i += 1;
+ buf[i] = data[j];
+ }
+ else {
+ node.tout = setTimeout(function () {
+ node.tout = null;
+ var m = new Buffer(i+1);
+ buf.copy(m,0,0,i+1);
+ node.send({"payload": m});
+ client.end();
+ m = null;
+ }, node.splitc);
+ i = 0;
+ buf[0] = data[j];
+ }
+ }
+ // count bytes into a buffer...
+ else if (node.out == "count") {
+ buf[i] = data[j];
+ i += 1;
+ if ( i >= node.serialConfig.count) {
+ node.send({"payload": buf});
+ client.end();
+ i = 0;
+ }
+ }
+ // look for a char
+ else {
+ buf[i] = data[j];
+ i += 1;
+ if (data[j] == node.splitc) {
+ var m = new Buffer(i);
+ buf.copy(m,0,0,i);
+ node.send({"payload": m});
+ client.end();
+ m = null;
+ i = 0;
+ }
+ }
+ }
+ }
+ });
+
+ client.on('end', function() {
+ //node.log('client disconnected');
+ node.connected = false;
+ node.status({});
+ client = null;
+ });
+
+ client.on('error', function() {
+ node.log('connect failed');
+ node.status({fill:"red",shape:"ring",text:"error"});
+ if (client) { client.end(); }
+ });
+
+ client.on('timeout',function() {
+ node.log('connect timeout');
+ if (client) {
+ client.end();
+ setTimeout(function() {
+ client.connect(node.port, node.server, function() {
+ //node.log('client connected');
+ node.connected = true;
+ client.write(msg.payload);
+ });
+ },reconnectTime);
+ }
+ });
+ }
+ else { client.write(msg.payload); }
+ });
+
+ this.on("close", function() {
+ if (client) { buf = null; client.end(); }
+ });
+
+ }
+ RED.nodes.registerType("tcp request",TcpGet);
+}
diff --git a/dgbuilder/core_nodes/io/32-udp.html b/dgbuilder/core_nodes/io/32-udp.html
new file mode 100644
index 00000000..1c2eed57
--- /dev/null
+++ b/dgbuilder/core_nodes/io/32-udp.html
@@ -0,0 +1,212 @@
+<!--
+ 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="fa fa-sign-in"></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="fa fa-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="fa fa-random"></i> Interface</label>
+ <input type="text" id="node-input-iface" placeholder="(optional) ip address of eth0">
+ </div>
+ <div class="form-row">
+ <label for="node-input-datatype"><i class="fa fa-sign-out"></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="fa fa-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.ip</b> and <b>msg.port</b> to the ip address and port from which the message was received.</p>
+ <p>On some systems you may need to be root to use ports below 1024 and/or broadcast.</p>
+</script>
+
+<script type="text/javascript">
+ RED.nodes.registerType('udp in',{
+ category: 'input',
+ color:"Silver",
+ defaults: {
+ name: {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="fa fa-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: 70px">
+ </div>
+ <div class="form-row node-input-addr">
+ <label for="node-input-addr" id="node-input-addr-label"><i class="fa fa-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="fa fa-random"></i> Interface</label>
+ <input type="text" id="node-input-iface" placeholder="(optional) ip address of eth0">
+ </div>
+ <div class="form-row">
+ <label for="node-input-outport-type">&nbsp;</label>
+ <select id="node-input-outport-type">
+ <option id="node-input-outport-type-random" value="random">use random local port</option>
+ <option value="fixed">bind to local port</option>
+ </select>
+ <input type="text" id="node-input-outport" style="width: 70px;" placeholder="port">
+ </div>
+ <div class="form-row">
+ <label>&nbsp;</label>
+ <input type="checkbox" id="node-input-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="fa fa-tag"></i> Name</label>
+ <input type="text" id="node-input-name" placeholder="Name">
+ </div>
+ <div class="form-tips">Tip: leave address and port blank if you want to set using <b>msg.ip</b> and <b>msg.port</b>.</div>
+ <script>
+ $("#node-input-multicast").change(function() {
+ var id = $("#node-input-multicast option:selected").val();
+ if (id !== "multi") {
+ $(".node-input-iface").hide();
+ $("#node-input-addr-label").html('<i class="fa fa-list"></i> Address');
+ $("#node-input-addr")[0].placeholder = 'destination ip';
+ }
+ else {
+ $(".node-input-iface").show();
+ $("#node-input-addr-label").html('<i class="fa fa-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>You may also use <b>msg.ip</b> and <b>msg.port</b> to set the destination values.<br/><b>Note</b>: the statically configured values have precedence.</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 ports below 1024 and/or broadcast.</p>
+</script>
+
+<script type="text/javascript">
+ RED.nodes.registerType('udp out',{
+ category: 'output',
+ color:"Silver",
+ defaults: {
+ name: {value:""},
+ addr: {value:""},
+ iface: {value:""},
+ port: {value:""},
+ outport: {value:""},
+ 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":"";
+ },
+ oneditprepare: function() {
+ var type = this.outport==""?"random":"fixed";
+ $("#node-input-outport-type option").filter(function() {
+ return $(this).val() == type;
+ }).attr('selected',true);
+
+ $("#node-input-outport-type").change(function() {
+ var type = $(this).children("option:selected").val();
+ if (type == "random") {
+ $("#node-input-outport").val("").hide();
+ } else {
+ $("#node-input-outport").show();
+ }
+ });
+
+ $("#node-input-outport-type").change();
+
+ $("#node-input-multicast").change(function() {
+ var type = $(this).children("option:selected").val();
+ if (type == "false") {
+ $("#node-input-outport-type-random").html("bind to random local port");
+ } else {
+ $("#node-input-outport-type-random").html("bind to target port");
+ }
+ });
+ $("#node-input-multicast").change();
+ }
+ });
+</script>
diff --git a/dgbuilder/core_nodes/io/32-udp.js b/dgbuilder/core_nodes/io/32-udp.js
new file mode 100644
index 00000000..a7968e3a
--- /dev/null
+++ b/dgbuilder/core_nodes/io/32-udp.js
@@ -0,0 +1,171 @@
+/**
+ * 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.
+ **/
+
+module.exports = function(RED) {
+ "use strict";
+ var dgram = require('dgram');
+
+ // The Input Node
+ function UDPin(n) {
+ RED.nodes.createNode(this,n);
+ this.group = n.group;
+ this.port = n.port;
+ 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) {
+ if ((err.code == "EACCES") && (node.port < 1024)) {
+ node.error("UDP access error, you may need root access for ports below 1024");
+ } else {
+ node.error("UDP error : "+err.code);
+ }
+ 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, ip:remote.address, port: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);
+ try {
+ server.setMulticastTTL(128);
+ server.addMembership(node.group,node.iface);
+ node.log("udp multicast group "+node.group);
+ } catch (e) {
+ if (e.errno == "EINVAL") {
+ node.error("Bad Multicast Address");
+ } else if (e.errno == "ENODEV") {
+ node.error("Must be ip address of the required interface");
+ } else {
+ node.error("Error :"+e.errno);
+ }
+ }
+ }
+ });
+
+ node.on("close", function() {
+ try {
+ server.close();
+ node.log('udp listener stopped');
+ } catch (err) {
+ node.error(err);
+ }
+ });
+
+ server.bind(node.port,node.iface);
+ }
+ 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.outport = n.outport||"";
+ 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
+
+ if (node.multicast != "false") {
+ if (node.outport == "") { node.outport = node.port; }
+ sock.bind(node.outport, function() { // have to bind before you can enable broadcast...
+ sock.setBroadcast(true); // turn on broadcast
+ if (node.multicast == "multi") {
+ try {
+ sock.setMulticastTTL(128);
+ sock.addMembership(node.addr,node.iface); // Add to the multicast group
+ node.log('udp multicast ready : '+node.outport+' -> '+node.addr+":"+node.port);
+ } catch (e) {
+ if (e.errno == "EINVAL") {
+ node.error("Bad Multicast Address");
+ } else if (e.errno == "ENODEV") {
+ node.error("Must be ip address of the required interface");
+ } else {
+ node.error("Error :"+e.errno);
+ }
+ }
+ } else {
+ node.log('udp broadcast ready : '+node.outport+' -> '+node.addr+":"+node.port);
+ }
+ });
+ } else if (node.outport != "") {
+ sock.bind(node.outport);
+ node.log('udp ready : '+node.outport+' -> '+node.addr+":"+node.port);
+ } else {
+ node.log('udp ready : '+node.addr+":"+node.port);
+ }
+
+ node.on("input", function(msg) {
+ if (msg.payload != null) {
+ var add = node.addr || msg.ip || "";
+ var por = node.port || msg.port || 0;
+ if (add == "") {
+ node.warn("udp: ip address not set");
+ } else if (por == 0) {
+ node.warn("udp: port not set");
+ } else if (isNaN(por) || (por < 1) || (por > 65535)) {
+ node.warn("udp: port number not valid");
+ } else {
+ var message;
+ if (node.base64) {
+ message = new Buffer(msg.payload, 'base64');
+ } else if (msg.payload instanceof Buffer) {
+ message = msg.payload;
+ } else {
+ message = new Buffer(""+msg.payload);
+ }
+ sock.send(message, 0, message.length, por, add, function(err, bytes) {
+ if (err) {
+ node.error("udp : "+err);
+ }
+ message = null;
+ });
+ }
+ }
+ });
+
+ node.on("close", function() {
+ try {
+ sock.close();
+ node.log('udp output stopped');
+ } catch (err) {
+ node.error(err);
+ }
+ });
+ }
+ RED.nodes.registerType("udp out",UDPout);
+}
diff --git a/dgbuilder/core_nodes/io/lib/mqtt.js b/dgbuilder/core_nodes/io/lib/mqtt.js
new file mode 100644
index 00000000..141a8889
--- /dev/null
+++ b/dgbuilder/core_nodes/io/lib/mqtt.js
@@ -0,0 +1,254 @@
+/**
+ * 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 util = require("util");
+var mqtt = require("mqtt");
+var events = require("events");
+//var inspect = require("sys").inspect;
+
+//var Client = module.exports.Client = function(
+
+var port = 1883;
+var host = "localhost";
+
+function MQTTClient(port,host) {
+ this.port = port||1883;
+ this.host = host||"localhost";
+ this.messageId = 1;
+ this.pendingSubscriptions = {};
+ this.inboundMessages = {};
+ this.lastOutbound = (new Date()).getTime();
+ this.lastInbound = (new Date()).getTime();
+ this.connected = false;
+
+ this._nextMessageId = function() {
+ this.messageId += 1;
+ if (this.messageId > 0xFFFF) {
+ this.messageId = 1;
+ }
+ return this.messageId;
+ }
+ events.EventEmitter.call(this);
+}
+util.inherits(MQTTClient, events.EventEmitter);
+
+MQTTClient.prototype.connect = function(options) {
+ if (!this.connected) {
+ var self = this;
+ options = options||{};
+ self.options = options;
+ self.options.keepalive = options.keepalive||15;
+ self.options.clean = self.options.clean||true;
+ self.options.protocolId = 'MQIsdp';
+ self.options.protocolVersion = 3;
+
+ self.client = mqtt.createConnection(this.port,this.host,function(err,client) {
+ if (err) {
+ self.connected = false;
+ clearInterval(self.watchdog);
+ self.connectionError = true;
+ //util.log('[mqtt] ['+self.uid+'] connection error 1 : '+inspect(err));
+ self.emit('connectionlost',err);
+ return;
+ }
+ client.on('close',function(e) {
+ //util.log('[mqtt] ['+self.uid+'] on close');
+ clearInterval(self.watchdog);
+ if (!self.connectionError) {
+ if (self.connected) {
+ self.connected = false;
+ self.emit('connectionlost',e);
+ } else {
+ self.emit('disconnect');
+ }
+ }
+ });
+ client.on('error',function(e) {
+ //util.log('[mqtt] ['+self.uid+'] on error : '+inspect(e));
+ clearInterval(self.watchdog);
+ if (self.connected) {
+ self.connected = false;
+ self.emit('connectionlost',e);
+ }
+ });
+ client.on('connack',function(packet) {
+ if (packet.returnCode == 0) {
+ self.watchdog = setInterval(function(self) {
+ var now = (new Date()).getTime();
+
+ //util.log('[mqtt] ['+self.uid+'] watchdog '+inspect({connected:self.connected,connectionError:self.connectionError,pingOutstanding:self.pingOutstanding,now:now,lastOutbound:self.lastOutbound,lastInbound:self.lastInbound}));
+
+ if (now - self.lastOutbound > self.options.keepalive*500 || now - self.lastInbound > self.options.keepalive*500) {
+ if (self.pingOutstanding) {
+ //util.log('[mqtt] ['+self.uid+'] watchdog pingOustanding - disconnect');
+ try {
+ self.client.disconnect();
+ } catch (err) {
+ }
+ } else {
+ //util.log('[mqtt] ['+self.uid+'] watchdog pinging');
+ self.lastOutbound = (new Date()).getTime();
+ self.lastInbound = (new Date()).getTime();
+ self.pingOutstanding = true;
+ self.client.pingreq();
+ }
+ }
+
+ },self.options.keepalive*500,self);
+ self.pingOutstanding = false;
+ self.lastInbound = (new Date()).getTime()
+ self.lastOutbound = (new Date()).getTime()
+ self.connected = true;
+ self.connectionError = false;
+ self.emit('connect');
+ } else {
+ self.connected = false;
+ self.emit('connectionlost');
+ }
+ });
+ client.on('suback',function(packet) {
+ self.lastInbound = (new Date()).getTime()
+ var topic = self.pendingSubscriptions[packet.messageId];
+ self.emit('subscribe',topic,packet.granted[0]);
+ delete self.pendingSubscriptions[packet.messageId];
+ });
+ client.on('unsuback',function(packet) {
+ self.lastInbound = (new Date()).getTime()
+ var topic = self.pendingSubscriptions[packet.messageId];
+ self.emit('unsubscribe',topic,packet.granted[0]);
+ delete self.pendingSubscriptions[packet.messageId];
+ });
+ client.on('publish',function(packet) {
+ self.lastInbound = (new Date()).getTime()
+ if (packet.qos < 2) {
+ var p = packet;
+ self.emit('message',p.topic,p.payload,p.qos,p.retain);
+ } else {
+ self.inboundMessages[packet.messageId] = packet;
+ this.lastOutbound = (new Date()).getTime()
+ self.client.pubrec(packet);
+ }
+ if (packet.qos == 1) {
+ this.lastOutbound = (new Date()).getTime()
+ self.client.puback(packet);
+ }
+ });
+
+ client.on('pubrel',function(packet) {
+ self.lastInbound = (new Date()).getTime()
+ var p = self.inboundMessages[packet.messageId];
+ if (p) {
+ self.emit('message',p.topic,p.payload,p.qos,p.retain);
+ delete self.inboundMessages[packet.messageId];
+ }
+ self.lastOutbound = (new Date()).getTime()
+ self.client.pubcomp(packet);
+ });
+
+ client.on('puback',function(packet) {
+ self.lastInbound = (new Date()).getTime()
+ // outbound qos-1 complete
+ });
+
+ client.on('pubrec',function(packet) {
+ self.lastInbound = (new Date()).getTime()
+ self.lastOutbound = (new Date()).getTime()
+ self.client.pubrel(packet);
+ });
+ client.on('pubcomp',function(packet) {
+ self.lastInbound = (new Date()).getTime()
+ // outbound qos-2 complete
+ });
+ client.on('pingresp',function(packet) {
+ //util.log('[mqtt] ['+self.uid+'] received pingresp');
+ self.lastInbound = (new Date()).getTime()
+ self.pingOutstanding = false;
+ });
+
+ this.lastOutbound = (new Date()).getTime()
+ this.connectionError = false;
+ client.connect(self.options);
+ });
+ }
+}
+
+MQTTClient.prototype.subscribe = function(topic,qos) {
+ var self = this;
+ if (self.connected) {
+ var options = {
+ subscriptions:[{topic:topic,qos:qos}],
+ messageId: self._nextMessageId()
+ };
+ this.pendingSubscriptions[options.messageId] = topic;
+ this.lastOutbound = (new Date()).getTime()
+ self.client.subscribe(options);
+ }
+}
+MQTTClient.prototype.unsubscribe = function(topic) {
+ var self = this;
+ if (self.connected) {
+ var options = {
+ topic:topic,
+ messageId: self._nextMessageId()
+ };
+ this.pendingSubscriptions[options.messageId] = topic;
+ this.lastOutbound = (new Date()).getTime()
+ self.client.unsubscribe(options);
+ }
+}
+
+MQTTClient.prototype.publish = function(topic,payload,qos,retain) {
+ var self = this;
+ if (self.connected) {
+
+ if (!Buffer.isBuffer(payload)) {
+ if (typeof payload === "object") {
+ payload = JSON.stringify(payload);
+ } else if (typeof payload !== "string") {
+ payload = ""+payload;
+ }
+ }
+ var options = {
+ topic: topic,
+ payload: payload,
+ qos: qos||0,
+ retain:retain||false
+ };
+ if (options.qos != 0) {
+ options.messageId = self._nextMessageId();
+ }
+ this.lastOutbound = (new Date()).getTime()
+ self.client.publish(options);
+ }
+}
+
+MQTTClient.prototype.disconnect = function() {
+ var self = this;
+ if (this.connected) {
+ this.connected = false;
+ try {
+ this.client.disconnect();
+ } catch(err) {
+ }
+ }
+}
+MQTTClient.prototype.isConnected = function() {
+ return this.connected;
+}
+module.exports.createClient = function(port,host) {
+ var mqtt_client = new MQTTClient(port,host);
+ return mqtt_client;
+}
+
diff --git a/dgbuilder/core_nodes/io/lib/mqttConnectionPool.js b/dgbuilder/core_nodes/io/lib/mqttConnectionPool.js
new file mode 100644
index 00000000..d15f0fc7
--- /dev/null
+++ b/dgbuilder/core_nodes/io/lib/mqttConnectionPool.js
@@ -0,0 +1,128 @@
+/**
+ * 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 util = require("util");
+var mqtt = require("./mqtt");
+var settings = require(process.env.NODE_RED_HOME+"/red/red").settings;
+
+var connections = {};
+
+function matchTopic(ts,t) {
+ if (ts == "#") {
+ return true;
+ }
+ var re = new RegExp("^"+ts.replace(/([\[\]\?\(\)\\\\$\^\*\.|])/g,"\\$1").replace(/\+/g,"[^/]+").replace(/\/#$/,"(\/.*)?")+"$");
+ return re.test(t);
+}
+
+module.exports = {
+ get: function(broker,port,clientid,username,password,will) {
+ var id = "["+(username||"")+":"+(password||"")+"]["+(clientid||"")+"]@"+broker+":"+port;
+ if (!connections[id]) {
+ connections[id] = function() {
+ var uid = (1+Math.random()*4294967295).toString(16);
+ var client = mqtt.createClient(port,broker);
+ client.uid = uid;
+ client.setMaxListeners(0);
+ var options = {keepalive:15};
+ options.clientId = clientid || 'mqtt_' + (1+Math.random()*4294967295).toString(16);
+ options.username = username;
+ options.password = password;
+ options.will = will;
+ var queue = [];
+ var subscriptions = [];
+ var connecting = false;
+ var obj = {
+ _instances: 0,
+ publish: function(msg) {
+ if (client.isConnected()) {
+ client.publish(msg.topic,msg.payload,msg.qos,msg.retain);
+ } else {
+ if (!connecting) {
+ connecting = true;
+ client.connect(options);
+ }
+ queue.push(msg);
+ }
+ },
+ subscribe: function(topic,qos,callback) {
+ subscriptions.push({topic:topic,qos:qos,callback:callback});
+ client.on('message',function(mtopic,mpayload,mqos,mretain) {
+ if (matchTopic(topic,mtopic)) {
+ callback(mtopic,mpayload,mqos,mretain);
+ }
+ });
+ if (client.isConnected()) {
+ client.subscribe(topic,qos);
+ }
+ },
+ on: function(a,b){
+ client.on(a,b);
+ },
+ once: function(a,b){
+ client.once(a,b);
+ },
+ connect: function() {
+ if (client && !client.isConnected() && !connecting) {
+ connecting = true;
+ client.connect(options);
+ }
+ },
+ disconnect: function() {
+ this._instances -= 1;
+ if (this._instances == 0) {
+ client.disconnect();
+ client = null;
+ delete connections[id];
+ }
+ }
+ };
+ client.on('connect',function() {
+ if (client) {
+ util.log('[mqtt] ['+uid+'] connected to broker tcp://'+broker+':'+port);
+ connecting = false;
+ for (var s in subscriptions) {
+ var topic = subscriptions[s].topic;
+ var qos = subscriptions[s].qos;
+ var callback = subscriptions[s].callback;
+ client.subscribe(topic,qos);
+ }
+ //console.log("connected - publishing",queue.length,"messages");
+ while(queue.length) {
+ var msg = queue.shift();
+ //console.log(msg);
+ client.publish(msg.topic,msg.payload,msg.qos,msg.retain);
+ }
+ }
+ });
+ client.on('connectionlost', function(err) {
+ util.log('[mqtt] ['+uid+'] connection lost to broker tcp://'+broker+':'+port);
+ connecting = false;
+ setTimeout(function() {
+ obj.connect();
+ }, settings.mqttReconnectTime||5000);
+ });
+ client.on('disconnect', function() {
+ connecting = false;
+ util.log('[mqtt] ['+uid+'] disconnected from broker tcp://'+broker+':'+port);
+ });
+
+ return obj
+ }();
+ }
+ connections[id]._instances += 1;
+ return connections[id];
+ }
+};
diff --git a/dgbuilder/core_nodes/logic/10-switch.html b/dgbuilder/core_nodes/logic/10-switch.html
new file mode 100644
index 00000000..4e02f446
--- /dev/null
+++ b/dgbuilder/core_nodes/logic/10-switch.html
@@ -0,0 +1,198 @@
+<!--
+ 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="switch">
+ <div class="form-row">
+ <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
+ <input type="text" id="node-input-name" placeholder="Name">
+ </div>
+ <div class="form-row" style="padding-top:10px;">
+ If msg.<input type="text" id="node-input-property" style="width: 200px;"/>
+ </div>
+ <div class="form-row">
+ <div id="node-input-rule-container-div" style="border-radius: 5px; height: 310px; padding: 5px; border: 1px solid #ccc; overflow-y:scroll;">
+ <ol id="node-input-rule-container" style=" list-style-type:none; margin: 0;">
+ </ol>
+ </div>
+ <a href="#" class="btn btn-mini" id="node-input-add-rule" style="margin-top: 4px;"><i class="fa fa-plus"></i> Add</a>
+ </div>
+ <div>
+ <select id="node-input-checkall" style="width:100%; margin-right:5px;">
+ <option value="true">checking all rules</option>
+ <option value="false">stopping after first match</option>
+ </select>
+ </div>
+</script>
+
+<script type="text/x-red" data-help-name="switch">
+ <p>A simple function node to route messages based on its properties.</p>
+ <p>When a message arrives, the selected property is evaluated against each
+ of the defined rules. The message is then sent to the output of <i>all</i>
+ rules that pass.</p>
+ <p>Note: the <i>otherwise</i> rule applies as a "not any of" the rules preceding it.</p>
+</script>
+
+<script type="text/javascript">
+ RED.nodes.registerType('switch', {
+ color: "#E2D96E",
+ category: 'function',
+ defaults: {
+ name: {value:""},
+ property: {value:"payload", required:true},
+ rules: {value:[{t:"eq", v:""}]},
+ checkall: {value:"true", required:true},
+ outputs: {value:1}
+ },
+ inputs: 1,
+ outputs: 1,
+ icon: "switch.png",
+ label: function() {
+ return this.name||"switch";
+ },
+ oneditprepare: function() {
+
+ var operators = [
+ {v:"eq",t:"=="},
+ {v:"neq",t:"!="},
+ {v:"lt",t:"<"},
+ {v:"lte",t:"<="},
+ {v:"gt",t:">"},
+ {v:"gte",t:">="},
+ {v:"btwn",t:"is between"},
+ {v:"cont",t:"contains"},
+ {v:"regex",t:"matches regex"},
+ {v:"true",t:"is true"},
+ {v:"false",t:"is false"},
+ {v:"null",t:"is null"},
+ {v:"nnull",t:"is not null"},
+ {v:"else",t:"otherwise"}
+ ];
+
+ function generateRule(i,rule) {
+ var container = $('<li/>',{style:"margin:0; padding:8px 0px; border-bottom: 1px solid #ccc;"});
+ var row = $('<div/>').appendTo(container);
+ var row2 = $('<div/>',{style:"padding-top: 5px; text-align: right;"}).appendTo(container);
+
+ var selectField = $('<select/>',{style:"width:120px; margin-left: 5px; text-align: center;"}).appendTo(row);
+ for (var d in operators) {
+ selectField.append($("<option></option>").val(operators[d].v).text(operators[d].t));
+ }
+
+ var valueField = $('<input/>',{class:"node-input-rule-value",type:"text",style:"margin-left: 5px; width: 145px;"}).appendTo(row);
+ var btwnField = $('<span/>').appendTo(row);
+ var btwnValueField = $('<input/>',{class:"node-input-rule-btwn-value",type:"text",style:"margin-left: 5px; width: 50px;"}).appendTo(btwnField);
+ btwnField.append(" and ");
+ var btwnValue2Field = $('<input/>',{class:"node-input-rule-btwn-value2",type:"text",style:"width: 50px;margin-left:2px;"}).appendTo(btwnField);
+
+ var finalspan = $('<span/>',{style:"float: right; margin-top: 3px;margin-right: 10px;"}).appendTo(row);
+ finalspan.append(' send to <span class="node-input-rule-index">'+i+'</span> ');
+
+ selectField.change(function() {
+ var type = selectField.children("option:selected").val();
+ if (type.length < 4) {
+ selectField.css({"width":"60px"});
+ } else if (type === "regex") {
+ selectField.css({"width":"147px"});
+ } else {
+ selectField.css({"width":"120px"});
+ }
+ if (type === "btwn") {
+ valueField.hide();
+ btwnField.show();
+ } else {
+ btwnField.hide();
+ if (type === "true" || type === "false" || type === "null" || type === "nnull" || type === "else") {
+ valueField.hide();
+ } else {
+ valueField.show();
+ }
+ }
+ });
+
+ var deleteButton = $('<a/>',{href:"#",class:"btn btn-mini", style:"margin-left: 5px;"}).appendTo(finalspan);
+ $('<i/>',{class:"fa fa-remove"}).appendTo(deleteButton);
+
+ deleteButton.click(function() {
+ container.css({"background":"#fee"});
+ container.fadeOut(300, function() {
+ $(this).remove();
+ $("#node-input-rule-container").children().each(function(i) {
+ $(this).find(".node-input-rule-index").html(i+1);
+ });
+
+ });
+ });
+
+ $("#node-input-rule-container").append(container);
+
+ selectField.find("option").filter(function() {return $(this).val() == rule.t;}).attr('selected',true);
+ if (rule.t == "btwn") {
+ btwnValueField.val(rule.v);
+ btwnValue2Field.val(rule.v2);
+ } else if (typeof rule.v != "undefined") {
+ valueField.val(rule.v);
+ }
+ selectField.change();
+ }
+
+ $("#node-input-add-rule").click(function() {
+ generateRule($("#node-input-rule-container").children().length+1,{t:"",v:"",v2:""});
+ $("#node-input-rule-container-div").scrollTop($("#node-input-rule-container-div").get(0).scrollHeight);
+ });
+
+ for (var i=0;i<this.rules.length;i++) {
+ var rule = this.rules[i];
+ generateRule(i+1,rule);
+ }
+
+ function switchDialogResize(ev,ui) {
+ $("#node-input-rule-container-div").css("height",(ui.size.height-260)+"px");
+ };
+
+ $( "#dialog" ).on("dialogresize", switchDialogResize);
+ $( "#dialog" ).one("dialogopen", function(ev) {
+ var size = $( "#dialog" ).dialog('option','sizeCache-switch');
+ if (size) {
+ switchDialogResize(null,{size:size});
+ }
+ });
+ $( "#dialog" ).one("dialogclose", function(ev,ui) {
+ $( "#dialog" ).off("dialogresize",switchDialogResize);
+ });
+ },
+ oneditsave: function() {
+ var rules = $("#node-input-rule-container").children();
+ var ruleset;
+ var node = this;
+ node.rules= [];
+ rules.each(function(i) {
+ var rule = $(this);
+ var type = rule.find("select option:selected").val();
+ var r = {t:type};
+ if (!(type === "true" || type === "false" || type === "null" || type === "nnull" || type === "else")) {
+ if (type === "btwn") {
+ r.v = rule.find(".node-input-rule-btwn-value").val();
+ r.v2 = rule.find(".node-input-rule-btwn-value2").val();
+ } else {
+ r.v = rule.find(".node-input-rule-value").val();
+ }
+ }
+ node.rules.push(r);
+ });
+ node.outputs = node.rules.length;
+ }
+ });
+</script>
diff --git a/dgbuilder/core_nodes/logic/10-switch.js b/dgbuilder/core_nodes/logic/10-switch.js
new file mode 100644
index 00000000..8bcb8571
--- /dev/null
+++ b/dgbuilder/core_nodes/logic/10-switch.js
@@ -0,0 +1,78 @@
+/**
+ * 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.
+ **/
+
+module.exports = function(RED) {
+ "use strict";
+ var operators = {
+ 'eq': function(a, b) { return a == b; },
+ 'neq': function(a, b) { return a != b; },
+ 'lt': function(a, b) { return a < b; },
+ 'lte': function(a, b) { return a <= b; },
+ 'gt': function(a, b) { return a > b; },
+ 'gte': function(a, b) { return a >= b; },
+ 'btwn': function(a, b, c) { return a >= b && a <= c; },
+ 'cont': function(a, b) { return (a + "").indexOf(b) != -1; },
+ 'regex': function(a, b) { return (a + "").match(new RegExp(b)); },
+ 'true': function(a) { return a === true; },
+ 'false': function(a) { return a === false; },
+ 'null': function(a) { return typeof a == "undefined"; },
+ 'nnull': function(a) { return typeof a != "undefined"; },
+ 'else': function(a) { return a === true; }
+ };
+
+ function SwitchNode(n) {
+ RED.nodes.createNode(this, n);
+ this.rules = n.rules;
+ this.property = n.property;
+ this.checkall = n.checkall || "true";
+ var propertyParts = n.property.split(".");
+ var node = this;
+
+ for (var i=0; i<this.rules.length; i+=1) {
+ var rule = this.rules[i];
+ if (!isNaN(Number(rule.v))) {
+ rule.v = Number(rule.v);
+ rule.v2 = Number(rule.v2);
+ }
+ }
+
+ this.on('input', function (msg) {
+ var onward = [];
+ try {
+ var prop = propertyParts.reduce(function (obj, i) {
+ return obj[i]
+ }, msg);
+ var elseflag = true;
+ for (var i=0; i<node.rules.length; i+=1) {
+ var rule = node.rules[i];
+ var test = prop;
+ if (rule.t == "else") { test = elseflag; elseflag = true; }
+ if (operators[rule.t](test,rule.v, rule.v2)) {
+ onward.push(msg);
+ elseflag = false;
+ if (node.checkall == "false") { break; }
+ } else {
+ onward.push(null);
+ }
+ }
+ this.send(onward);
+ } catch(err) {
+ node.warn(err);
+ }
+ });
+ }
+ RED.nodes.registerType("switch", SwitchNode);
+}
diff --git a/dgbuilder/core_nodes/logic/15-change.html b/dgbuilder/core_nodes/logic/15-change.html
new file mode 100644
index 00000000..702dd807
--- /dev/null
+++ b/dgbuilder/core_nodes/logic/15-change.html
@@ -0,0 +1,139 @@
+<!--
+ 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="change">
+ <div>
+ <select id="node-input-action" style="width:95%; margin-right:5px;">
+ <option value="replace">Set the value of the message property</option>
+ <option value="change">Search/replace the value of the message property</option>
+ <option value="delete">Delete the message property</option>
+ </select>
+ </div>
+ <div class="form-row" style="padding-top:10px;" id="node-prop1-row">
+ <label for="node-input-property">called</label> msg.<input type="text" id="node-input-property" style="width: 63%;"/>
+ </div>
+ <div class="form-row" id="node-from-row">
+ <label for="node-input-from" id="node-input-f"></label>
+ <input type="text" id="node-input-from" placeholder="this"/>
+ </div>
+ <div class="form-row" id="node-to-row">
+ <label for="node-input-to" id="node-input-t"></label>
+ <input type="text" id="node-input-to" placeholder="that"/>
+ </div>
+ <div class="form-row" id="node-reg-row">
+ <label>&nbsp;</label>
+ <input type="checkbox" id="node-input-reg" style="display: inline-block; width: auto; vertical-align: top;">
+ <label for="node-input-reg" style="width: 70%;">Use regular expressions ?</label>
+ </div>
+ <div class="form-tips" id="node-tip"></div>
+ <br/>
+ <div class="form-row">
+ <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
+ <input type="text" id="node-input-name" placeholder="Name">
+ </div>
+</script>
+
+<script type="text/x-red" data-help-name="change">
+ <p>A simple function node to change, replace, add or delete properties of a message.</p>
+ <p>When a message arrives, the selected property is modified by the defined rules.
+ The message is then sent to the output.</p>
+ <p><b>Note:</b> Replace only operates on <b>strings</b>. Anything else will be passed straight through.</p>
+</script>
+
+<script type="text/javascript">
+ RED.nodes.registerType('change', {
+ color: "#E2D96E",
+ category: 'function',
+ defaults: {
+ action: {value:"replace",required:true},
+ property: {value:"payload",required:true},
+ from: {value:"",validate: function(v) {
+ if (this.action == "change" && this.reg) {
+ try {
+ var re = new RegExp(this.from, "g");
+ return true;
+ } catch(err) {
+ return false;
+ }
+ }
+ return true;
+ }},
+ to: {value:""},
+ reg: {value:false},
+ name: {value:""}
+ },
+ inputs: 1,
+ outputs: 1,
+ icon: "swap.png",
+ label: function() {
+ if (this.name) {
+ return this.name;
+ }
+ if (this.action == "replace") {
+ return "set msg."+this.property;
+ } else {
+ return this.action+" msg."+this.property
+ }
+ },
+ labelStyle: function() {
+ return this.name ? "node_label_italic" : "";
+ },
+ oneditprepare: function() {
+ if (this.reg === null) { $("#node-input-reg").prop('checked', true); }
+ $("#node-input-action").change( function() {
+ var a = $("#node-input-action").val();
+ if (a === "replace") {
+ $("#node-input-todo").html("called");
+ //$("#node-input-f").html("name");
+ $("#node-input-t").html("to");
+ $("#node-from-row").hide();
+ $("#node-to-row").show();
+ $("#node-reg-row").hide();
+ $("#node-tip").show();
+ $("#node-tip").html("Tip: expects a new property name and either a fixed value OR the full name of another message property eg: msg.sentiment.score");
+ }
+ if (a === "delete") {
+ $("#node-input-todo").html("called");
+ //$("#node-input-f").html("called");
+ //$("#node-input-t").html("to");
+ $("#node-from-row").hide();
+ $("#node-to-row").hide();
+ $("#node-reg-row").hide();
+ $("#node-tip").hide();
+ }
+ if (a === "change") {
+ $("#node-input-todo").html("called");
+ $("#node-input-f").html("Search for");
+ $("#node-input-t").html("replace with");
+ $("#node-from-row").show();
+ $("#node-to-row").show();
+ $("#node-reg-row").show();
+ $("#node-tip").show();
+ $("#node-tip").html("Tip: only works on string properties. If regular expressions are used, the <i>replace with</i> field can contain capture results, eg $1.");
+ }
+ //if (a === "replace") {
+ // $("#node-input-todo").html("called");
+ // //$("#node-input-f").html("with");
+ // $("#node-input-t").html("with");
+ // $("#node-from-row").hide();
+ // $("#node-to-row").show();
+ // $("#node-tip").html("Tip: accepts either a fixed value OR the full name of another msg.property eg: msg.sentiment.score");
+ //}
+ });
+ $("#node-input-action").change();
+ }
+ });
+</script>
diff --git a/dgbuilder/core_nodes/logic/15-change.js b/dgbuilder/core_nodes/logic/15-change.js
new file mode 100644
index 00000000..b7ef62e1
--- /dev/null
+++ b/dgbuilder/core_nodes/logic/15-change.js
@@ -0,0 +1,74 @@
+/**
+ * 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.
+ **/
+
+module.exports = function(RED) {
+ "use strict";
+ function ChangeNode(n) {
+ RED.nodes.createNode(this, n);
+ this.action = n.action;
+ this.property = n.property || "";
+ this.from = n.from || " ";
+ this.to = n.to || " ";
+ this.reg = (n.reg === null || n.reg);
+ var node = this;
+ if (node.reg === false) {
+ this.from = this.from.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
+ }
+ var makeNew = function( stem, path, value ) {
+ var lastPart = (arguments.length === 3) ? path.pop() : false;
+ for (var i = 0; i < path.length; i++) {
+ stem = stem[path[i]] = stem[path[i]] || {};
+ }
+ if (lastPart) { stem = stem[lastPart] = value; }
+ return stem;
+ };
+
+ this.on('input', function (msg) {
+ if (node.action == "change") {
+ try {
+ node.re = new RegExp(this.from, "g");
+ } catch (e) {
+ node.error(e.message);
+ }
+ if (typeof msg[node.property] === "string") {
+ msg[node.property] = (msg[node.property]).replace(node.re, node.to);
+ }
+ }
+ //else if (node.action == "replace") {
+ //if (node.to.indexOf("msg.") == 0) {
+ //msg[node.property] = eval(node.to);
+ //}
+ //else {
+ //msg[node.property] = node.to;
+ //}
+ //}
+ else if (node.action == "replace") {
+ if (node.to.indexOf("msg.") === 0) {
+ makeNew( msg, node.property.split("."), eval(node.to) );
+ }
+ else {
+ makeNew( msg, node.property.split("."), node.to );
+ }
+ //makeNew( msg, node.property.split("."), node.to );
+ }
+ else if (node.action == "delete") {
+ delete(msg[node.property]);
+ }
+ node.send(msg);
+ });
+ }
+ RED.nodes.registerType("change", ChangeNode);
+}
diff --git a/dgbuilder/core_nodes/logic/16-range.html b/dgbuilder/core_nodes/logic/16-range.html
new file mode 100644
index 00000000..5f87128f
--- /dev/null
+++ b/dgbuilder/core_nodes/logic/16-range.html
@@ -0,0 +1,81 @@
+<!--
+ 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="range">
+ <div class="form-row">
+ <label for="node-input-action"><i class="fa fa-dot-circle-o"></i> Action</label>
+ <select id="node-input-action" style="width:70%; margin-right:5px;">
+ <option value="scale">Scale msg.payload</option>
+ <option value="clamp">Scale and limit to the target range</option>
+ <option value="roll">Scale and wrap within the target range</option>
+ </select>
+ </div>
+ <br/>
+ <div class="form-row"><i class="fa fa-sign-in"></i> Map the input range:</div>
+ <div class="form-row"><label></label>
+ from: <input type="text" id="node-input-minin" placeholder="e.g. 0" style="width:100px;"/>
+ &nbsp;&nbsp;to: <input type="text" id="node-input-maxin" placeholder="e.g. 99" style="width:100px;"/>
+ </div>
+ <div class="form-row"><i class="fa fa-sign-out"></i> to the result range:</div>
+ <div class="form-row"><label></label>
+ from: <input type="text" id="node-input-minout" placeholder="e.g. 0" style="width:100px;"/>
+ &nbsp;&nbsp;to: <input type="text" id="node-input-maxout" placeholder="e.g. 255" style="width:100px;"/>
+ </div>
+ <br/>
+ <div class="form-row"><label></label>
+ <input type="checkbox" id="node-input-round" style="display: inline-block; width: auto; vertical-align: top;">
+ <label style="width: auto;" for="node-input-round">Round result to the nearest integer?</label></input>
+ </div>
+ <br/>
+ <div class="form-row">
+ <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
+ <input type="text" id="node-input-name" placeholder="Name">
+ </div>
+ <div class="form-tips" id="node-tip">Tip: This node ONLY works with numbers.</div>
+</script>
+
+<script type="text/x-red" data-help-name="range">
+ <p>A simple function node to remap numeric input values to another scale.</p>
+ <p>Currently only does a linear scaling.</p>
+ <p><b>Note:</b> This only operates on <b>numbers</b>. Anything else will try to be made into a number and rejected if that fails.</p>
+ <p><i>Scale and limit to target range</i> means that the result will never be outside the range specified within the result range.</p>
+ <p><i>Scale and wrap within the target range</i> means that the result will essentially be a "modulo-style" wrap-around within the result range.</p>
+</script>
+
+<script type="text/javascript">
+ RED.nodes.registerType('range', {
+ color: "#E2D96E",
+ category: 'function',
+ defaults: {
+ minin: {value:"",required:true,validate:RED.validators.number()},
+ maxin: {value:"",required:true,validate:RED.validators.number()},
+ minout: {value:"",required:true,validate:RED.validators.number()},
+ maxout: {value:"",required:true,validate:RED.validators.number()},
+ action: {value:"scale"},
+ round: {value:false},
+ name: {value:""}
+ },
+ inputs: 1,
+ outputs: 1,
+ icon: "range.png",
+ label: function() {
+ return this.name || "range";
+ },
+ labelStyle: function() {
+ return this.name ? "node_label_italic" : "";
+ }
+ });
+</script>
diff --git a/dgbuilder/core_nodes/logic/16-range.js b/dgbuilder/core_nodes/logic/16-range.js
new file mode 100644
index 00000000..ec39342a
--- /dev/null
+++ b/dgbuilder/core_nodes/logic/16-range.js
@@ -0,0 +1,48 @@
+/**
+ * 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.
+ **/
+
+module.exports = function(RED) {
+ "use strict";
+ function RangeNode(n) {
+ RED.nodes.createNode(this, n);
+ this.action = n.action;
+ this.round = n.round || false;
+ this.minin = Number(n.minin);
+ this.maxin = Number(n.maxin);
+ this.minout = Number(n.minout);
+ this.maxout = Number(n.maxout);
+ var node = this;
+
+ this.on('input', function (msg) {
+ var n = Number(msg.payload);
+ if (!isNaN(n)) {
+ if (node.action == "clamp") {
+ if (n < node.minin) { n = node.minin; }
+ if (n > node.maxin) { n = node.maxin; }
+ }
+ if (node.action == "roll") {
+ if (n >= node.maxin) { n = (n - node.minin) % (node.maxin - node.minin) + node.minin; }
+ if (n < node.minin) { n = (n - node.minin) % (node.maxin - node.minin) + node.maxin; }
+ }
+ msg.payload = ((n - node.minin) / (node.maxin - node.minin) * (node.maxout - node.minout)) + node.minout;
+ if (node.round) { msg.payload = Math.round(msg.payload); }
+ node.send(msg);
+ }
+ else { node.log("Not a number: "+msg.payload); }
+ });
+ }
+ RED.nodes.registerType("range", RangeNode);
+}
diff --git a/dgbuilder/core_nodes/parsers/70-CSV.html b/dgbuilder/core_nodes/parsers/70-CSV.html
new file mode 100644
index 00000000..5632246f
--- /dev/null
+++ b/dgbuilder/core_nodes/parsers/70-CSV.html
@@ -0,0 +1,123 @@
+
+<!--
+ 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="csv">
+ <div class="form-row">
+ <label for="node-input-temp"><i class="fa fa-list"></i> Columns</label>
+ <input type="text" id="node-input-temp" placeholder="comma-separated column names">
+ </div>
+ <div class="form-row">
+ <label for="node-input-select-sep"><i class="fa fa-text-width"></i> Separator</label>
+ <select style="width: 150px" id="node-input-select-sep">
+ <option value=",">comma</option>
+ <option value="\t">tab</option>
+ <option value=" ">space</option>
+ <option value=";">semicolon</option>
+ <option value=":">colon</option>
+ <option value="">other...</option>
+ </select>
+ <input style="width: 40px;" type="text" id="node-input-sep" pattern=".">
+ </div>
+
+ <div class="form-row">
+ <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
+ <input type="text" id="node-input-name" placeholder="Name">
+ </div>
+ <hr align="middle"/>
+ <div class="form-row">
+ <label style="width: 100%;"><i class="fa fa-gears"></i> CSV-to-Object options</label>
+ <label style="margin-left: 10px; margin-right: -10px;"><i class="fa fa-sign-in"></i> Input</label>
+ <input style="width: 30px" type="checkbox" id="node-input-hdrin"><label style="width: auto;" for="node-input-hdrin">first row contains column names</span>
+ </div>
+ <div class="form-row">
+ <label style="margin-left: 10px; margin-right: -10px;"><i class="fa fa-sign-out"></i> Output</label>
+ <select type="text" id="node-input-multi" style="width: 250px;">
+ <option value="one">a message per row</option>
+ <option value="mult">a single message [array]</option>
+ </select>
+ </div>
+ <hr align="middle"/>
+ <div class="form-row">
+ <label style="width: 100%;"><i class="fa fa-gears"></i> Object-to-CSV options</label>
+ <label style="margin-left: 10px; margin-right: -10px;"><i class="fa fa-sign-in"></i> Output</label>
+ <input style="width: 30px" type="checkbox" id="node-input-hdrout"><label style="width: auto;" for="node-input-hdrout">include column name row</span>
+ </div>
+ <div class="form-row">
+ <label style="margin-left: 10px; margin-right: -10px;"><i class="fa fa-align-left"></i> Newline</label>
+ <select style="width: 150px" id="node-input-ret">
+ <option value='\n'>Linux (\n)</option>
+ <option value='\r'>Mac (\r)</option>
+ <option value='\r\n'>Windows (\r\n)</option>
+ </select>
+ </div>
+</script>
+
+<script type="text/x-red" data-help-name="csv">
+ <p>A function that parses the <b>msg.payload</b> to convert csv to/from a javascript object.
+ Places the result in the payload.</p>
+ <p>If the input is a string it tries to parse it as CSV and creates a javascript object.</p>
+ <p>If the input is a javascript object it tries to build a CSV string.</p>
+ <p>The columns template should contain an ordered list of column headers. For csv input these become the property names.
+ For csv output these specify the properties to extract from the object and the order for the csv.</p>
+ <p><b>Note:</b> the columns should always be specified comma separated - even if another separator is chosen for the data.</p>
+ </script>
+
+<script type="text/javascript">
+ RED.nodes.registerType('csv',{
+ category: 'function',
+ color:"#DEBD5C",
+ defaults: {
+ name: {value:""},
+ sep: {value:',',required:true,validate:RED.validators.regex(/^.{1,2}$/)},
+ //quo: {value:'"',required:true},
+ hdrin: {value:""},
+ hdrout: {value:""},
+ multi: {value:"one",required:true},
+ ret: {value:'\\n'},
+ temp: {value:""}
+ },
+ inputs:1,
+ outputs:1,
+ icon: "arrow-in.png",
+ label: function() {
+ return this.name||"csv";
+ },
+ labelStyle: function() {
+ return this.name?"node_label_italic":"";
+ },
+ oneditprepare: function() {
+ if (this.sep == "," || this.sep == "\\t" || this.sep == ";" || this.sep == ":" || this.sep == " ") {
+ $("#node-input-select-sep").val(this.sep);
+ $("#node-input-sep").hide();
+ } else {
+ $("#node-input-select-sep").val("");
+ $("#node-input-sep").val(this.sep);
+ $("#node-input-sep").show();
+ }
+ $("#node-input-select-sep").change(function() {
+ var v = $("#node-input-select-sep option:selected").val();
+ $("#node-input-sep").val(v);
+ if (v == "") {
+ $("#node-input-sep").val("");
+ $("#node-input-sep").show().focus();
+ } else {
+ $("#node-input-sep").hide();
+ }
+ });
+ }
+ });
+</script>
diff --git a/dgbuilder/core_nodes/parsers/70-CSV.js b/dgbuilder/core_nodes/parsers/70-CSV.js
new file mode 100644
index 00000000..56aa7c72
--- /dev/null
+++ b/dgbuilder/core_nodes/parsers/70-CSV.js
@@ -0,0 +1,157 @@
+/**
+ * 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.
+ **/
+
+module.exports = function(RED) {
+ "use strict";
+ function CSVNode(n) {
+ RED.nodes.createNode(this,n);
+ this.template = n.temp.split(",");
+ this.sep = (n.sep || ',').replace("\\t","\t").replace("\\n","\n").replace("\\r","\r");
+ this.quo = '"';
+ this.ret = (n.ret || "\n").replace("\\n","\n").replace("\\r","\r");
+ this.winflag = (this.ret === "\r\n");
+ this.lineend = "\n";
+ this.multi = n.multi || "one";
+ this.hdrin = n.hdrin || false;
+ this.hdrout = n.hdrout || false;
+ this.goodtmpl = true;
+ var node = this;
+
+ // pass in an array of column names to be trimed, de-quoted and retrimed
+ var clean = function(col) {
+ for (var t = 0; t < col.length; t++) {
+ col[t] = col[t].trim(); // remove leading and trailing whitespace
+ if (col[t].charAt(0) === '"' && col[t].charAt(col[t].length -1) === '"') {
+ // remove leading and trailing quotes (if they exist) - and remove whitepace again.
+ col[t] = col[t].substr(1,col[t].length -2).trim();
+ }
+ }
+ if ((col.length === 1) && (col[0] === "")) { node.goodtmpl = false; }
+ else { node.goodtmpl = true; }
+ return col;
+ }
+ node.template = clean(node.template);
+
+ this.on("input", function(msg) {
+ if (msg.hasOwnProperty("payload")) {
+ if (typeof msg.payload == "object") { // convert object to CSV string
+ try {
+ var ou = "";
+ if (node.hdrout) {
+ ou += node.template.join(node.sep) + node.ret;
+ }
+ if (!Array.isArray(msg.payload)) { msg.payload = [ msg.payload ]; }
+ for (var s = 0; s < msg.payload.length; s++) {
+ for (var t=0; t < node.template.length; t++) {
+
+ // aaargh - resorting to eval here - but fairly contained front and back.
+ var p = RED.util.ensureString(eval("msg.payload[s]."+node.template[t]));
+
+ if (p === "undefined") { p = ""; }
+ if (p.indexOf(node.sep) != -1) { // add quotes if any "commas"
+ ou += node.quo + p + node.quo + node.sep;
+ }
+ else if (p.indexOf(node.quo) != -1) { // add double quotes if any quotes
+ p = p.replace(/"/g, '""');
+ ou += node.quo + p + node.quo + node.sep;
+ }
+ else { ou += p + node.sep; } // otherwise just add
+ }
+ ou = ou.slice(0,-1) + node.ret; // remove final "comma" and add "newline"
+ }
+ node.send({payload:ou});
+ }
+ catch(e) { node.log(e); }
+ }
+ else if (typeof msg.payload == "string") { // convert CSV string to object
+ try {
+ var f = true; // flag to indicate if inside or outside a pair of quotes true = outside.
+ var j = 0; // pointer into array of template items
+ var k = [""]; // array of data for each of the template items
+ var o = {}; // output object to build up
+ var a = []; // output array is needed for multiline option
+ var first = true; // is this the first line
+ var tmp = "";
+
+ // For now we are just going to assume that any \r or \n means an end of line...
+ // got to be a weird csv that has singleton \r \n in it for another reason...
+
+ // Now process the whole file/line
+ for (var i = 0; i < msg.payload.length; i++) {
+ if ((node.hdrin === true) && first) { // if the template is in the first line
+ if ((msg.payload[i] === "\n")||(msg.payload[i] === "\r")) { // look for first line break
+ node.template = clean(tmp.split(node.sep));
+ first = false;
+ }
+ else { tmp += msg.payload[i]; }
+ }
+ else {
+ if (msg.payload[i] === node.quo) { // if it's a quote toggle inside or outside
+ f = !f;
+ if (msg.payload[i-1] === node.quo) { k[j] += '\"'; } // if it's a quotequote then it's actually a quote
+ }
+ else if ((msg.payload[i] === node.sep) && f) { // if we are outside of quote (ie valid separator
+ if (!node.goodtmpl) { node.template[j] = "col"+(j+1); }
+ if ( node.template[j] && (node.template[j] !== "") && (k[j] !== "" ) ) {
+ if (!isNaN(Number(k[j]))) { k[j] = Number(k[j]); }
+ o[node.template[j]] = k[j];
+ }
+ j += 1;
+ k[j] = "";
+ }
+ else if (f && ((msg.payload[i] === "\n") || (msg.payload[i] === "\r"))) { // handle multiple lines
+ //console.log(j,k,o,k[j]);
+ if ( node.template[j] && (node.template[j] !== "") && (k[j] !== "") ) {
+ if (!isNaN(Number(k[j]))) { k[j] = Number(k[j]); }
+ else { k[j].replace(/\r$/,''); }
+ o[node.template[j]] = k[j];
+ }
+ if (JSON.stringify(o) !== "{}") { // don't send empty objects
+ if (node.multi === "one") { node.send({payload:o}); } // either send
+ else { a.push(o); } // or add to the array
+ }
+ j = 0;
+ k = [""];
+ o = {};
+ }
+ else { // just add to the part of the message
+ k[j] += msg.payload[i];
+ }
+ }
+ }
+ // Finished so finalize and send anything left
+ //console.log(j,k,o,k[j]);
+ if (!node.goodtmpl) { node.template[j] = "col"+(j+1); }
+ if ( node.template[j] && (node.template[j] !== "") && (k[j] !== "") ) {
+ if (!isNaN(Number(k[j]))) { k[j] = Number(k[j]); }
+ else { k[j].replace(/\r$/,''); }
+ o[node.template[j]] = k[j];
+ }
+ msg.payload = o;
+ if (JSON.stringify(o) !== "{}") { // don't send empty objects
+ if (node.multi === "one") { node.send({payload:o}); } // either send
+ else { a.push(o); } // or add to the aray
+ }
+ if (node.multi !== "one") { node.send({payload:a}); } // finally send the array
+ }
+ catch(e) { node.log(e); }
+ }
+ else { node.log("This node only handles csv strings or js objects."); }
+ }
+ });
+ }
+ RED.nodes.registerType("csv",CSVNode);
+}
diff --git a/dgbuilder/core_nodes/parsers/70-HTML.html b/dgbuilder/core_nodes/parsers/70-HTML.html
new file mode 100644
index 00000000..2b73b49c
--- /dev/null
+++ b/dgbuilder/core_nodes/parsers/70-HTML.html
@@ -0,0 +1,73 @@
+<!--
+ 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="html">
+ <div class="form-row">
+ <label for="node-input-tag"><i class="fa fa-filter"></i> Select</label>
+ <input type="text" id="node-input-tag" placeholder="h1">
+ </div>
+ <div class="form-row">
+ <label for="node-input-ret"><i class="fa fa-sign-out"></i> Output</label>
+ <select id="node-input-ret" style="width:73% !important">
+ <option value="html">the html content of the elements</option>
+ <option value="text">only the text content of the elements</option>
+ <!-- <option value="attr">an object of any attributes</option> -->
+ <!-- <option value="val">return the value from a form element</option> -->
+ </select>
+ </div>
+ <div class="form-row">
+ <label for="node-input-as">&nbsp;</label>
+ <select id="node-input-as" style="width:73% !important">
+ <option value="single">as a single message containing an array</option>
+ <option value="multi">as multiple messages, one for each element</option>
+ </select>
+ </div>
+ <br/>
+ <div class="form-row">
+ <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
+ <input type="text" id="node-input-name" placeholder="Name">
+ </div>
+ <div class="form-tips">Tip: The <b>Select</b> value is a <a href="http://api.jquery.com/category/selectors/" target="_new"><i><u>jQuery</u></i></a> style selector.</div>
+</script>
+
+<script type="text/x-red" data-help-name="html">
+ <p>Extracts elements from an html document held in <b>msg.payload</b> using a selector.</p>
+ <p>The selector uses the <a href="http://api.jquery.com/category/selectors/" target="_new">jQuery syntax</a>.</p>
+ <p>The result is either a single message with a payload containing an array of the matched elements, or multiple
+ messages that each contain a matched element.</p>
+</script>
+
+<script type="text/javascript">
+ RED.nodes.registerType('html',{
+ category: 'function',
+ color:"#DEBD5C",
+ defaults: {
+ tag: {value:""},
+ ret: {value:"html"},
+ as: {value:"single"},
+ name: {value:""}
+ },
+ inputs:1,
+ outputs:1,
+ icon: "jq.png",
+ label: function() {
+ return this.name||this.tag||"html";
+ },
+ labelStyle: function() {
+ return this.name?"node_label_italic":"";
+ }
+ });
+</script>
diff --git a/dgbuilder/core_nodes/parsers/70-HTML.js b/dgbuilder/core_nodes/parsers/70-HTML.js
new file mode 100644
index 00000000..7a9450f3
--- /dev/null
+++ b/dgbuilder/core_nodes/parsers/70-HTML.js
@@ -0,0 +1,60 @@
+/**
+ * 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.
+ **/
+
+module.exports = function(RED) {
+ "use strict";
+ var cheerio = require('cheerio');
+
+ function CheerioNode(n) {
+ RED.nodes.createNode(this,n);
+ this.tag = n.tag || "h1";
+ this.ret = n.ret || "html";
+ this.as = n.as || "single";
+ var node = this;
+ this.on("input", function(msg) {
+ try {
+ var $ = cheerio.load(msg.payload);
+ var pay = [];
+ $(node.tag).each(function() {
+ if (node.as === "multi") {
+ var pay2 = null;
+ if (node.ret === "html") { pay2 = $(this).html(); }
+ if (node.ret === "text") { pay2 = $(this).text(); }
+ //if (node.ret === "attr") { pay2 = $(this)[0]["attribs"]; }
+ //if (node.ret === "val") { pay2 = $(this).val(); }
+ if (pay2) {
+ msg.payload = pay2;
+ node.send(msg);
+ }
+ }
+ if (node.as === "single") {
+ if (node.ret === "html") { pay.push( $(this).html() ); }
+ if (node.ret === "text") { pay.push( $(this).text() ); }
+ //if (node.ret === "attr") { pay.push( $(this)[0]["attribs"] ); }
+ //if (node.ret === "val") { pay.push( $(this).val() ); }
+ }
+ });
+ if ((node.as === "single") && (pay.length !== 0)) {
+ msg.payload = pay;
+ node.send(msg);
+ }
+ } catch (error) {
+ node.log('Error: '+error.message);
+ }
+ });
+ }
+ RED.nodes.registerType("html",CheerioNode);
+}
diff --git a/dgbuilder/core_nodes/parsers/70-JSON.html b/dgbuilder/core_nodes/parsers/70-JSON.html
new file mode 100644
index 00000000..864974ab
--- /dev/null
+++ b/dgbuilder/core_nodes/parsers/70-JSON.html
@@ -0,0 +1,47 @@
+<!--
+ 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="json">
+ <div class="form-row">
+ <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
+ <input type="text" id="node-input-name" placeholder="Name">
+ </div>
+</script>
+
+<script type="text/x-red" data-help-name="json">
+ <p>A function that parses the <b>msg.payload</b> to convert a JSON string to/from a javascript object. Places the result back into the payload.</p>
+ <p>If the input is a JSON string it tries to parse it to a javascript object.</p>
+ <p>If the input is a javascript object it creates a JSON string.</p>
+</script>
+
+<script type="text/javascript">
+ RED.nodes.registerType('json',{
+ category: 'function',
+ color:"#DEBD5C",
+ defaults: {
+ name: {value:""}
+ },
+ inputs:1,
+ outputs:1,
+ icon: "arrow-in.png",
+ label: function() {
+ return this.name||"json";
+ },
+ labelStyle: function() {
+ return this.name?"node_label_italic":"";
+ }
+ });
+</script>
diff --git a/dgbuilder/core_nodes/parsers/70-JSON.js b/dgbuilder/core_nodes/parsers/70-JSON.js
new file mode 100644
index 00000000..e216bd4f
--- /dev/null
+++ b/dgbuilder/core_nodes/parsers/70-JSON.js
@@ -0,0 +1,46 @@
+/**
+ * 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.
+ **/
+
+module.exports = function(RED) {
+ "use strict";
+ var util = require("util");
+
+ function JSONNode(n) {
+ RED.nodes.createNode(this,n);
+ var node = this;
+ this.on("input", function(msg) {
+ if (msg.hasOwnProperty("payload")) {
+ if (typeof msg.payload === "string") {
+ try {
+ msg.payload = JSON.parse(msg.payload);
+ node.send(msg);
+ }
+ catch(e) { node.log(e+ "\n"+msg.payload); }
+ }
+ else if (typeof msg.payload === "object") {
+ if (!Buffer.isBuffer(msg.payload) ) {
+ if (!util.isArray(msg.payload)) {
+ msg.payload = JSON.stringify(msg.payload);
+ node.send(msg);
+ }
+ }
+ }
+ else { node.log("dropped: "+msg.payload); }
+ }
+ });
+ }
+ RED.nodes.registerType("json",JSONNode);
+}
diff --git a/dgbuilder/core_nodes/parsers/70-XML.html b/dgbuilder/core_nodes/parsers/70-XML.html
new file mode 100644
index 00000000..8b0a2843
--- /dev/null
+++ b/dgbuilder/core_nodes/parsers/70-XML.html
@@ -0,0 +1,48 @@
+<!--
+ 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="xml">
+ <div class="form-row">
+ <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
+ <input type="text" id="node-input-name" placeholder="Name">
+ </div>
+</script>
+
+<script type="text/x-red" data-help-name="xml">
+ <p>A function that parses the <b>msg.payload</b> to convert xml to/from a javascript object. Places the result in the payload.</p>
+ <p>If the input is a string it tries to parse it as XML and creates a javascript object.</p>
+ <p>If the input is a javascript object it tries to build an XML string.</p>
+ <p>See <a href="https://github.com/Leonidas-from-XIV/node-xml2js/blob/master/README.md" target="_new">the xml2js docs <i>here</i></a> for more information.</p>
+</script>
+
+<script type="text/javascript">
+ RED.nodes.registerType('xml',{
+ category: 'function',
+ color:"#DEBD5C",
+ defaults: {
+ name: {value:""}
+ },
+ inputs:1,
+ outputs:1,
+ icon: "arrow-in.png",
+ label: function() {
+ return this.name||"xml";
+ },
+ labelStyle: function() {
+ return this.name?"node_label_italic":"";
+ }
+ });
+</script>
diff --git a/dgbuilder/core_nodes/parsers/70-XML.js b/dgbuilder/core_nodes/parsers/70-XML.js
new file mode 100644
index 00000000..931de7f5
--- /dev/null
+++ b/dgbuilder/core_nodes/parsers/70-XML.js
@@ -0,0 +1,46 @@
+/**
+ * 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.
+ **/
+
+module.exports = function(RED) {
+ "use strict";
+ var xml2js = require('xml2js');
+ var parseString = xml2js.parseString;
+ var builder = new xml2js.Builder({renderOpts:{pretty:false}});
+
+ function XMLNode(n) {
+ RED.nodes.createNode(this,n);
+ var node = this;
+ this.on("input", function(msg) {
+ if (msg.hasOwnProperty("payload")) {
+ if (typeof msg.payload == "object") {
+ msg.payload = builder.buildObject(msg.payload);
+ node.send(msg);
+ }
+ else if (typeof msg.payload == "string") {
+ parseString(msg.payload, {strict:true,async:true}, function (err, result) {
+ if (err) { node.error(err); }
+ else {
+ msg.payload = result;
+ node.send(msg);
+ }
+ });
+ }
+ else { node.log("This node only handles xml strings or js objects."); }
+ }
+ });
+ }
+ RED.nodes.registerType("xml",XMLNode);
+}
diff --git a/dgbuilder/core_nodes/social/27-twitter.html b/dgbuilder/core_nodes/social/27-twitter.html
new file mode 100644
index 00000000..99d45213
--- /dev/null
+++ b/dgbuilder/core_nodes/social/27-twitter.html
@@ -0,0 +1,223 @@
+<!--
+ 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="twitter-credentials">
+ <div class="form-row" id="node-config-twitter-row"></div>
+ <input type="hidden" id="node-config-input-screen_name">
+</script>
+
+<script type="text/javascript">
+(function() {
+ var twitterConfigNodeId = null;
+ var twitterConfigNodeIntervalId = null;
+
+ function showTwitterAuthStart() {
+ var pathname = document.location.pathname;
+ if (pathname.slice(-1) != "/") {
+ pathname += "/";
+ }
+ var callback = encodeURIComponent(location.protocol+"//"+location.hostname+":"+location.port+pathname+"twitter-credentials/"+twitterConfigNodeId+"/auth/callback");
+ $("#node-config-dialog-ok").button("disable");
+ $("#node-config-twitter-row").html('<div style="text-align: center; margin-top: 20px; "><a class="btn" id="node-config-twitter-start" href="twitter-credentials/'+twitterConfigNodeId+'/auth?callback='+callback+'" target="_blank">Click here to authenticate with Twitter.</a></div>');
+ $("#node-config-twitter-start").click(function() {
+ twitterConfigNodeIntervalId = window.setTimeout(pollTwitterCredentials,2000);
+ });
+ }
+ function updateTwitterScreenName(sn) {
+ $("#node-config-input-screen_name").val(sn);
+ $("#node-config-twitter-row").html('<label><i class="fa fa-user"></i> Twitter ID</label><span class="input-xlarge uneditable-input">'+sn+'</span>');
+ }
+ function pollTwitterCredentials(e) {
+ $.getJSON('credentials/twitter-credentials/'+twitterConfigNodeId,function(data) {
+ if (data.screen_name) {
+ updateTwitterScreenName(data.screen_name);
+ twitterConfigNodeIntervalId = null;
+ $("#node-config-dialog-ok").button("enable");
+ } else {
+ twitterConfigNodeIntervalId = window.setTimeout(pollTwitterCredentials,2000);
+ }
+ })
+ }
+ RED.nodes.registerType('twitter-credentials',{
+ category: 'config',
+ defaults: {
+ screen_name: {value:""}
+ },
+ credentials: {
+ screen_name: {type:"text"},
+ access_token: {type: "password"},
+ access_token_secret: {type:"password"}
+ },
+
+ label: function() {
+ return this.screen_name;
+ },
+ exportable: false,
+ oneditprepare: function() {
+ twitterConfigNodeId = this.id;
+ if (!this.screen_name || this.screen_name == "") {
+ showTwitterAuthStart();
+ } else {
+ if (this.credentials.screen_name) {
+ updateTwitterScreenName(this.credentials.screen_name);
+ } else {
+ showTwitterAuthStart();
+ }
+ }
+ },
+ oneditsave: function() {
+ if (twitterConfigNodeIntervalId) {
+ window.clearTimeout(twitterConfigNodeIntervalId);
+ }
+ },
+ oneditcancel: function(adding) {
+ if (twitterConfigNodeIntervalId) {
+ window.clearTimeout(twitterConfigNodeIntervalId);
+ }
+ }
+ });
+})();
+</script>
+
+<script type="text/x-red" data-template-name="twitter in">
+ <div class="form-row">
+ <label for="node-input-twitter"><i class="fa fa-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="fa fa-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>
+ <option value="user">the tweets of specific users</option>
+ <option value="dm">your direct messages</option>
+ </select>
+ </div>
+ <div class="form-row" id="node-input-tags-row">
+ <label for="node-input-tags"><i class="fa fa-tags"></i> <span id="node-input-tags-label">for</span></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="fa fa-tag"></i> Name</label>
+ <input type="text" id="node-input-name" placeholder="Name">
+ </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. Can be used to search either:
+ <ul><li>the public or a user's stream for tweets containing the configured search term</li>
+ <li>all tweets by specific users</li>
+ <li>direct messages received by the authenticated user</li>
+ </ul></p>
+ <p>Use space for <i>and</i> and comma , for <i>or</i> when searching for multiple terms.</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>
+ <p>Sets <b>msg.tweet</b> to the full tweet object as documented by <a href="https://dev.twitter.com/docs/platform-objects/tweets">Twitter</a>.
+ <p><b>Note:</b> when set to a specific user's tweets, or your direct messages, the node is subject to
+ Twitter's API rate limiting. If you deploy the flows multiple times within a 15 minute window, you may
+ exceed the limit and will see errors from the node. These errors will clear when the current 15 minute window
+ passes.</p>
+</script>
+
+<script type="text/javascript">
+ RED.nodes.registerType('twitter in',{
+ category: 'social-input',
+ color:"#C0DEED",
+ defaults: {
+ twitter: {type:"twitter-credentials",required:true},
+ tags: {value:"",validate:function(v) { return this.user == "dm" || v.length > 0;}},
+ user: {value:"false",required:true},
+ name: {value:""},
+ topic: {value:"tweets"}
+ },
+ inputs:0,
+ outputs:1,
+ icon: "twitter.png",
+ label: function() {
+ if (this.name) {
+ return this.name;
+ }
+ if (this.user == "dm") {
+ var user = RED.nodes.node(this.twitter);
+ return (user?user.label()+" ":"")+"DMs";
+ } else if (this.user == "user") {
+ return this.tags+" tweets";
+ }
+ return this.tags;
+ },
+ labelStyle: function() {
+ return this.name?"node_label_italic":"";
+ },
+ oneditprepare: function() {
+ $("#node-input-user").change(function() {
+ var type = $("#node-input-user option:selected").val();
+ if (type == "user") {
+ $("#node-input-tags-row").show();
+ $("#node-input-tags-label").html("User");
+ $("#node-input-tags").attr("placeholder","comma-separated @twitter handles");
+ } else if (type == "dm") {
+ $("#node-input-tags-row").hide();
+ } else {
+ $("#node-input-tags-row").show();
+ $("#node-input-tags-label").html("for");
+ $("#node-input-tags").attr("placeholder","comma-separated words, @ids, #hashtags");
+ }
+
+ });
+ $("#node-input-user").change();
+
+ }
+ });
+</script>
+
+
+<script type="text/x-red" data-template-name="twitter out">
+ <div class="form-row">
+ <label for="node-input-twitter"><i class="fa fa-user"></i> Twitter</label>
+ <input type="text" id="node-input-twitter">
+ </div>
+ <div class="form-row">
+ <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
+ <input type="text" id="node-input-name" placeholder="Name">
+ </div>
+</script>
+
+<script type="text/x-red" data-help-name="twitter out">
+ <p>Twitter out node. Tweets the <b>msg.payload</b>.</p>
+ <p>If <b>msg.media</b> exists and is a Buffer object, this node will treat it
+ as an image and attach it to the tweet.</p>
+</script>
+
+<script type="text/javascript">
+ RED.nodes.registerType('twitter out',{
+ category: 'social-output',
+ color:"#C0DEED",
+ defaults: {
+ twitter: {type:"twitter-credentials",required:true},
+ name: {value:"Tweet"}
+ },
+ inputs:1,
+ outputs:0,
+ icon: "twitter.png",
+ align: "right",
+ label: function() {
+ return this.name;
+ }
+ });
+</script>
diff --git a/dgbuilder/core_nodes/social/27-twitter.js b/dgbuilder/core_nodes/social/27-twitter.js
new file mode 100644
index 00000000..5cacd9eb
--- /dev/null
+++ b/dgbuilder/core_nodes/social/27-twitter.js
@@ -0,0 +1,347 @@
+/**
+ * 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.
+ **/
+
+module.exports = function(RED) {
+ "use strict";
+ var ntwitter = require('twitter-ng');
+ var OAuth= require('oauth').OAuth;
+ var request = require('request');
+
+ function TwitterNode(n) {
+ RED.nodes.createNode(this,n);
+ this.screen_name = n.screen_name;
+ }
+ RED.nodes.registerType("twitter-credentials",TwitterNode,{
+ credentials: {
+ screen_name: {type:"text"},
+ access_token: {type: "password"},
+ access_token_secret: {type:"password"}
+ }
+ });
+
+ function TwitterInNode(n) {
+ RED.nodes.createNode(this,n);
+ this.active = true;
+ this.user = n.user;
+ //this.tags = n.tags.replace(/ /g,'');
+ this.tags = n.tags;
+ this.twitter = n.twitter;
+ this.topic = n.topic||"tweets";
+ this.twitterConfig = RED.nodes.getNode(this.twitter);
+ var credentials = RED.nodes.getCredentials(this.twitter);
+
+ if (credentials && credentials.screen_name == this.twitterConfig.screen_name) {
+ var twit = new ntwitter({
+ consumer_key: "OKjYEd1ef2bfFolV25G5nQ",
+ consumer_secret: "meRsltCktVMUI8gmggpXett7WBLd1k0qidYazoML6g",
+ access_token_key: credentials.access_token,
+ access_token_secret: credentials.access_token_secret
+ });
+
+
+ //setInterval(function() {
+ // twit.get("/application/rate_limit_status.json",null,function(err,cb) {
+ // console.log("direct_messages:",cb["resources"]["direct_messages"]);
+ // });
+ //
+ //},10000);
+
+ var node = this;
+ if (this.user === "user") {
+ node.poll_ids = [];
+ node.since_ids = {};
+ var users = node.tags.split(",");
+ for (var i=0;i<users.length;i++) {
+ var user = users[i].replace(" ","");
+ twit.getUserTimeline({
+ screen_name:user,
+ trim_user:0,
+ count:1
+ },function() {
+ var u = user+"";
+ return function(err,cb) {
+ if (err) {
+ node.error(err);
+ return;
+ }
+ if (cb[0]) {
+ node.since_ids[u] = cb[0].id_str;
+ } else {
+ node.since_ids[u] = '0';
+ }
+ node.poll_ids.push(setInterval(function() {
+ twit.getUserTimeline({
+ screen_name:u,
+ trim_user:0,
+ since_id:node.since_ids[u]
+ },function(err,cb) {
+ if (cb) {
+ for (var t=cb.length-1;t>=0;t-=1) {
+ var tweet = cb[t];
+ var where = tweet.user.location||"";
+ var la = tweet.lang || tweet.user.lang;
+ //console.log(tweet.user.location,"=>",tweet.user.screen_name,"=>",pay);
+ var msg = { topic:node.topic+"/"+tweet.user.screen_name, payload:tweet.text, location:where, lang:la, tweet:tweet };
+ node.send(msg);
+ if (t == 0) {
+ node.since_ids[u] = tweet.id_str;
+ }
+ }
+ }
+ if (err) {
+ node.error(err);
+ }
+ });
+ },60000));
+ }
+ }());
+ }
+ } else if (this.user === "dm") {
+ node.poll_ids = [];
+ twit.getDirectMessages({
+ screen_name:node.twitterConfig.screen_name,
+ trim_user:0,
+ count:1
+ },function(err,cb) {
+ if (err) {
+ node.error(err);
+ return;
+ }
+ if (cb[0]) {
+ node.since_id = cb[0].id_str;
+ } else {
+ node.since_id = '0';
+ }
+ node.poll_ids.push(setInterval(function() {
+ twit.getDirectMessages({
+ screen_name:node.twitterConfig.screen_name,
+ trim_user:0,
+ since_id:node.since_id
+ },function(err,cb) {
+ if (cb) {
+ for (var t=cb.length-1;t>=0;t-=1) {
+ var tweet = cb[t];
+ var msg = { topic:node.topic+"/"+tweet.sender.screen_name, payload:tweet.text, tweet:tweet };
+ node.send(msg);
+ if (t == 0) {
+ node.since_id = tweet.id_str;
+ }
+ }
+ }
+ if (err) {
+ node.error(err);
+ }
+ });
+ },120000));
+ });
+
+ } else if (this.tags !== "") {
+ try {
+ var thing = 'statuses/filter';
+ if (this.user === "true") { thing = 'user'; }
+ var st = { track: [node.tags] };
+ var bits = node.tags.split(",");
+ if ((bits.length > 0) && (bits.length % 4 == 0)) {
+ if ((Number(bits[0]) < Number(bits[2])) && (Number(bits[1]) < Number(bits[3]))) {
+ st = { locations: node.tags };
+ }
+ else {
+ node.log("possible bad geo area format. Should be lower-left lon, lat, upper-right lon, lat");
+ }
+ }
+
+ var setupStream = function() {
+ if (node.active) {
+ twit.stream(thing, st, function(stream) {
+ //console.log(st);
+ //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) {
+ //console.log(tweet.user);
+ if (tweet.user !== undefined) {
+ var where = tweet.user.location||"";
+ var la = tweet.lang || tweet.user.lang;
+ //console.log(tweet.user.location,"=>",tweet.user.screen_name,"=>",pay);
+ var msg = { topic:node.topic+"/"+tweet.user.screen_name, payload:tweet.text, location:where, lang:la, tweet:tweet };
+ node.send(msg);
+ }
+ });
+ stream.on('limit', function(tweet) {
+ node.log("tweet rate limit hit");
+ });
+ stream.on('error', function(tweet,rc) {
+ if (rc == 420) {
+ node.warn("Twitter rate limit hit");
+ } else {
+ node.warn("Stream error:"+tweet.toString()+" ("+rc+")");
+ }
+ setTimeout(setupStream,10000);
+ });
+ stream.on('destroy', function (response) {
+ if (this.active) {
+ node.warn("twitter ended unexpectedly");
+ setTimeout(setupStream,10000);
+ }
+ });
+ });
+ }
+ }
+ setupStream();
+ }
+ catch (err) {
+ node.error(err);
+ }
+ } else {
+ this.error("Invalid tag property");
+ }
+ } else {
+ this.error("missing twitter credentials");
+ }
+
+ this.on('close', function() {
+ if (this.stream) {
+ this.active = false;
+ this.stream.destroy();
+ }
+ if (this.poll_ids) {
+ for (var i=0;i<this.poll_ids.length;i++) {
+ clearInterval(this.poll_ids[i]);
+ }
+ }
+ });
+ }
+ RED.nodes.registerType("twitter in",TwitterInNode);
+
+
+ function TwitterOutNode(n) {
+ RED.nodes.createNode(this,n);
+ this.topic = n.topic;
+ this.twitter = n.twitter;
+ this.twitterConfig = RED.nodes.getNode(this.twitter);
+ var credentials = RED.nodes.getCredentials(this.twitter);
+ var node = this;
+
+ if (credentials && credentials.screen_name == this.twitterConfig.screen_name) {
+ var twit = new ntwitter({
+ consumer_key: "OKjYEd1ef2bfFolV25G5nQ",
+ consumer_secret: "meRsltCktVMUI8gmggpXett7WBLd1k0qidYazoML6g",
+ access_token_key: credentials.access_token,
+ access_token_secret: credentials.access_token_secret
+ });
+ node.on("input", function(msg) {
+ node.status({fill:"blue",shape:"dot",text:"tweeting"});
+
+ if (msg.payload.length > 140) {
+ msg.payload = msg.payload.slice(0,139);
+ node.warn("Tweet greater than 140 : truncated");
+ }
+
+ if (msg.media && Buffer.isBuffer(msg.media)) {
+ var apiUrl = "https://api.twitter.com/1.1/statuses/update_with_media.json";
+ var signedUrl = oa.signUrl(apiUrl,
+ credentials.access_token,
+ credentials.access_token_secret,
+ "POST");
+
+ var r = request.post(signedUrl,function(err,httpResponse,body) {
+ if (err) {
+ node.error(err.toString());
+ node.status({fill:"red",shape:"ring",text:"failed"});
+ } else {
+ var response = JSON.parse(body);
+ if (body.errors) {
+ var errorList = body.errors.map(function(er) { return er.code+": "+er.message }).join(", ");
+ node.error("tweet failed: "+errorList);
+ node.status({fill:"red",shape:"ring",text:"failed"});
+ } else {
+ node.status({});
+ }
+ }
+ });
+ var form = r.form();
+ form.append("status",msg.payload);
+ form.append("media[]",msg.media,{filename:"image"});
+
+ } else {
+ twit.updateStatus(msg.payload, function (err, data) {
+ if (err) {
+ node.status({fill:"red",shape:"ring",text:"failed"});
+ node.error(err);
+ }
+ node.status({});
+ });
+ }
+ });
+ }
+ }
+ RED.nodes.registerType("twitter out",TwitterOutNode);
+
+ var oa = new OAuth(
+ "https://api.twitter.com/oauth/request_token",
+ "https://api.twitter.com/oauth/access_token",
+ "OKjYEd1ef2bfFolV25G5nQ",
+ "meRsltCktVMUI8gmggpXett7WBLd1k0qidYazoML6g",
+ "1.0",
+ null,
+ "HMAC-SHA1"
+ );
+
+ RED.httpAdmin.get('/twitter-credentials/:id/auth', function(req, res){
+ var credentials = {};
+ oa.getOAuthRequestToken({
+ oauth_callback: req.query.callback
+ },function(error, oauth_token, oauth_token_secret, results){
+ if (error) {
+ var resp = '<h2>Oh no!</h2>'+
+ '<p>Something went wrong with the authentication process. The following error was returned:<p>'+
+ '<p><b>'+error.statusCode+'</b>: '+error.data+'</p>'+
+ '<p>One known cause of this type of failure is if the clock is wrong on system running Node-RED.';
+ res.send(resp)
+ } else {
+ credentials.oauth_token = oauth_token;
+ credentials.oauth_token_secret = oauth_token_secret;
+ res.redirect('https://twitter.com/oauth/authorize?oauth_token='+oauth_token)
+ RED.nodes.addCredentials(req.params.id,credentials);
+ }
+ });
+ });
+
+ RED.httpAdmin.get('/twitter-credentials/:id/auth/callback', function(req, res, next){
+ var credentials = RED.nodes.getCredentials(req.params.id);
+ credentials.oauth_verifier = req.query.oauth_verifier;
+
+ oa.getOAuthAccessToken(
+ credentials.oauth_token,
+ credentials.token_secret,
+ credentials.oauth_verifier,
+ function(error, oauth_access_token, oauth_access_token_secret, results){
+ if (error){
+ console.log(error);
+ res.send("yeah something broke.");
+ } else {
+ credentials = {};
+ credentials.access_token = oauth_access_token;
+ credentials.access_token_secret = oauth_access_token_secret;
+ credentials.screen_name = "@"+results.screen_name;
+ RED.nodes.addCredentials(req.params.id,credentials);
+ res.send("<html><head></head><body>Authorised - you can close this window and return to Node-RED</body></html>");
+ }
+ }
+ );
+ });
+}
diff --git a/dgbuilder/core_nodes/social/32-feedparse.html b/dgbuilder/core_nodes/social/32-feedparse.html
new file mode 100644
index 00000000..a7954277
--- /dev/null
+++ b/dgbuilder/core_nodes/social/32-feedparse.html
@@ -0,0 +1,57 @@
+<!--
+ 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="feedparse">
+ <div class="form-row">
+ <label for="node-input-url"><i class="fa fa-globe"></i> Feed url</label>
+ <input type="text" id="node-input-url">
+ </div>
+ <div class="form-row">
+ <label for="node-input-interval"><i class="fa fa-repeat"></i> Repeat <span style="font-size: 0.9em;">(M)</span></label>
+ <input type="text" id="node-input-interval" placeholder="minutes">
+ </div>
+ <div class="form-row">
+ <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
+ <input type="text" id="node-input-name" placeholder="Name">
+ </div>
+ <!-- <div class="form-tips"></div> -->
+</script>
+
+<script type="text/x-red" data-help-name="feedparse">
+ <p>Monitors an RSS/atom feed for new entries.</p>
+</script>
+
+<script type="text/javascript">
+ RED.nodes.registerType('feedparse',{
+ category: 'advanced-input',
+ color:"#C0DEED",
+ defaults: {
+ name: {value:""},
+ url: {value:"", required:true},
+ interval: { value:15, required: true,validate:RED.validators.number()}
+ },
+ inputs:0,
+ outputs:1,
+ icon: "feed.png",
+ label: function() {
+ return this.name||this.url;
+ },
+ labelStyle: function() {
+ return this.name?"node_label_italic":"";
+ }
+ });
+
+</script>
diff --git a/dgbuilder/core_nodes/social/32-feedparse.js b/dgbuilder/core_nodes/social/32-feedparse.js
new file mode 100644
index 00000000..97630e7d
--- /dev/null
+++ b/dgbuilder/core_nodes/social/32-feedparse.js
@@ -0,0 +1,71 @@
+/**
+ * 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.
+ **/
+
+module.exports = function(RED) {
+ "use strict";
+ var FeedParser = require("feedparser");
+ var request = require("request");
+
+ function FeedParseNode(n) {
+ RED.nodes.createNode(this,n);
+ this.url = n.url;
+ this.interval = (parseInt(n.interval)||15)*60000;
+ var node = this;
+ this.interval_id = null;
+ this.seen = {};
+ if (this.url !== "") {
+ var getFeed = function() {
+ request(node.url,function(err) {
+ if (err) node.error(err);
+ })
+ .pipe(new FeedParser({feedurl:node.url}))
+ .on('error', function(error) {
+ node.error(error);
+ })
+ .on('meta', function (meta) {})
+ .on('readable', function () {
+ var stream = this, article;
+ while (article = stream.read()) {
+ if (!(article.guid in node.seen) || ( node.seen[article.guid] != 0 && node.seen[article.guid] != article.date.getTime())) {
+ node.seen[article.guid] = article.date?article.date.getTime():0;
+ var msg = {
+ topic:article.origlink||article.link,
+ payload: article.description,
+ article: article
+ };
+ node.send(msg);
+ }
+ }
+ })
+ .on('end', function () {
+ });
+ };
+ this.interval_id = setInterval(getFeed,node.interval);
+ getFeed();
+
+ } else {
+ this.error("Invalid url");
+ }
+ }
+
+ RED.nodes.registerType("feedparse",FeedParseNode);
+
+ FeedParseNode.prototype.close = function() {
+ if (this.interval_id != null) {
+ clearInterval(this.interval_id);
+ }
+ }
+}
diff --git a/dgbuilder/core_nodes/social/61-email.html b/dgbuilder/core_nodes/social/61-email.html
new file mode 100644
index 00000000..37397083
--- /dev/null
+++ b/dgbuilder/core_nodes/social/61-email.html
@@ -0,0 +1,189 @@
+<!--
+ Copyright 2013,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="e-mail">
+ <div class="form-row">
+ <label for="node-input-name"><i class="fa fa-envelope"></i> To</label>
+ <input type="text" id="node-input-name" placeholder="email@address.com">
+ </div>
+ <!-- <div class="form-row">
+ <label for="node-input-pin"><i class="fa fa-asterisk"></i> Service</label>
+ <select type="text" id="node-input-pin" style="width: 150px;">
+ <option value="-" disabled> </option>
+ <option value="DynectEmail">DynectEmail</option>
+ <option value="Gmail">Gmail</option>
+ <option value="hot.ee">hot.ee</option>
+ <option value="Hotmail">Hotmail</option>
+ <option value="iCloud">iCloud</option>
+ <option value="mail.ee">mail.ee</option>
+ <option value="Mail.Ru">Mail.Ru</option>
+ <option value="Mailgun">Mailgun</option>
+ <option value="Mailjet">Mailjet</option>
+ <option value="Mandrill">Mandrill</option>
+ <option value="Postmark">Postmark</option>
+ <option value="QQ">QQ</option>
+ <option value="QQex">QQex</option>
+ <option value="SendGrid">SendGrid</option>
+ <option value="SendCloud">SendCloud</option>
+ <option value="SES">SES</option>
+ <option value="Yahoo">Yahoo</option>
+ <option value="yandex">yandex</option>
+ <option value="Zoho">Zoho</option>
+ </select>
+ </div> -->
+ <div class="form-row">
+ <label for="node-input-server"><i class="fa fa-globe"></i> Server</label>
+ <input type="text" id="node-input-server" placeholder="smtp.gmail.com">
+ </div>
+ <div class="form-row">
+ <label for="node-input-port"><i class="fa fa-random"></i> Port</label>
+ <input type="text" id="node-input-port" placeholder="465">
+ </div>
+ <div class="form-row">
+ <label for="node-input-userid"><i class="fa fa-user"></i> Userid</label>
+ <input type="text" id="node-input-userid">
+ </div>
+ <div class="form-row">
+ <label for="node-input-password"><i class="fa fa-lock"></i> Password</label>
+ <input type="password" id="node-input-password">
+ </div>
+ <br/>
+ <div class="form-row">
+ <label for="node-input-dname"><i class="fa fa-tag"></i> Name</label>
+ <input type="text" id="node-input-dname" placeholder="Name">
+ </div>
+ <div class="form-tips" id="node-tip"><b>Note:</b> Copied credentials from global emailkeys.js file.</div>
+</script>
+
+<script type="text/x-red" data-help-name="e-mail">
+ <p>Sends the <b>msg.payload</b> as an email, with a subject of <b>msg.topic</b>.</p>
+ <!-- <p>It sends the message to the configured recipient <i>only</i>.</p> -->
+ <p>You may dynamically overide the default recipient by setting a <b>msg.to</b> property.</p>
+ <!-- <p><b>msg.topic</b> is used to set the subject of the email, and <b>msg.payload</b> is the body text.</p> -->
+</script>
+
+<script type="text/javascript">
+(function() {
+ RED.nodes.registerType('e-mail',{
+ category: 'social-output',
+ color:"#c7e9c0",
+ defaults: {
+ server: {value:"smtp.gmail.com",required:true},
+ port: {value:"465",required:true},
+ name: {value:"",required:true},
+ dname: {value:""}
+ },
+ credentials: {
+ userid: {type:"text"},
+ password: {type: "password"},
+ global: { type:"boolean"}
+ },
+
+ inputs:1,
+ outputs:0,
+ icon: "envelope.png",
+ align: "right",
+ label: function() {
+ return this.dname||this.name||"email";
+ },
+ labelStyle: function() {
+ return (this.dname||!this.topic)?"node_label_italic":"";
+ },
+ oneditprepare: function() {
+ if (this.credentials.global) {
+ $('#node-tip').show();
+ } else {
+ $('#node-tip').hide();
+ };
+ }
+ });
+})();
+</script>
+
+
+<script type="text/x-red" data-template-name="e-mail in">
+ <div class="form-row node-input-repeat">
+ <label for="node-input-repeat"><i class="fa fa-repeat"></i> Check Repeat (S)</label>
+ <input type="text" id="node-input-repeat" placeholder="300">
+ </div>
+ <div class="form-row">
+ <label for="node-input-server"><i class="fa fa-globe"></i> Server</label>
+ <input type="text" id="node-input-server" placeholder="imap.gmail.com">
+ </div>
+ <div class="form-row">
+ <label for="node-input-port"><i class="fa fa-random"></i> Port</label>
+ <input type="text" id="node-input-port" placeholder="993">
+ </div>
+ <div class="form-row">
+ <label for="node-input-userid"><i class="fa fa-user"></i> Userid</label>
+ <input type="text" id="node-input-userid">
+ </div>
+ <div class="form-row">
+ <label for="node-config-input-password"><i class="fa fa-lock"></i> Password</label>
+ <input type="password" id="node-input-password">
+ </div>
+ <br/>
+ <div class="form-row">
+ <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
+ <input type="text" id="node-input-name" placeholder="Name">
+ </div>
+ <div class="form-tips" id="node-tip"><b>Note:</b> Copied credentials from global emailkeys.js file.</div>
+ <div id="node-input-tip" class="form-tips">Tip: <b>ONLY</b> retrieves the single most recent email.</div>
+</script>
+
+<script type="text/x-red" data-help-name="e-mail in">
+ <p>Repeatedly gets a <b>single email</b> from an IMAP server and forwards on as a msg if not already seen.</p>
+ <p>The subject is loaded into <b>msg.topic</b> and <b>msg.payload</b> is the plain text body.
+ If there is text/html then that is returned in <b>msg.html</b>. <b>msg.from</b> and <b>msg.date</b> are also set if you need them.</p>
+ <p>Uses the imap module.</p>
+ <p><b>Note:</b> this node <i>only</i> gets the most recent single email from the inbox, so set the repeat (polling) time appropriately.</p>
+</script>
+
+<script type="text/javascript">
+(function() {
+ RED.nodes.registerType('e-mail in',{
+ category: 'social-input',
+ color:"#c7e9c0",
+ defaults: {
+ repeat: {value:"300",required:true},
+ server: {value:"imap.gmail.com",required:true},
+ port: {value:"993",required:true},
+ name: {value:""}
+ },
+ credentials: {
+ userid: {type:"text"},
+ password: {type: "password"},
+ global: { type:"boolean"}
+ },
+ inputs:0,
+ outputs:1,
+ icon: "envelope.png",
+ label: function() {
+ return this.name||"email";
+ },
+ labelStyle: function() {
+ return (this.name||!this.topic)?"node_label_italic":"";
+ },
+ oneditprepare: function() {
+ if (this.credentials.global) {
+ $('#node-tip').show();
+ } else {
+ $('#node-tip').hide();
+ };
+ }
+ });
+})();
+</script>
diff --git a/dgbuilder/core_nodes/social/61-email.js b/dgbuilder/core_nodes/social/61-email.js
new file mode 100644
index 00000000..7d0f8cb9
--- /dev/null
+++ b/dgbuilder/core_nodes/social/61-email.js
@@ -0,0 +1,246 @@
+/**
+ * Copyright 2013,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.
+ **/
+
+module.exports = function(RED) {
+ "use strict";
+ var nodemailer = require("nodemailer");
+ var Imap = require('imap');
+
+ //console.log(nodemailer.Transport.transports.SMTP.wellKnownHosts);
+
+ try {
+ var globalkeys = RED.settings.email || require(process.env.NODE_RED_HOME+"/../emailkeys.js");
+ } catch(err) {
+ }
+
+ function EmailNode(n) {
+ RED.nodes.createNode(this,n);
+ this.topic = n.topic;
+ this.name = n.name;
+ this.outserver = n.server;
+ this.outport = n.port;
+ var flag = false;
+ if (this.credentials && this.credentials.hasOwnProperty("userid")) {
+ this.userid = this.credentials.userid;
+ } else {
+ if (globalkeys) {
+ this.userid = globalkeys.user;
+ flag = true;
+ } else {
+ this.error("No e-mail userid set");
+ }
+ }
+ if (this.credentials && this.credentials.hasOwnProperty("password")) {
+ this.password = this.credentials.password;
+ } else {
+ if (globalkeys) {
+ this.password = globalkeys.pass;
+ flag = true;
+ } else {
+ this.error("No e-mail password set");
+ }
+ }
+ if (flag) {
+ RED.nodes.addCredentials(n.id,{userid:this.userid, password:this.password, global:true});
+ }
+ var node = this;
+
+ var smtpTransport = nodemailer.createTransport({
+ host: node.outserver,
+ port: node.outport,
+ secure: true,
+ auth: {
+ user: node.userid,
+ pass: node.password
+ }
+ });
+
+ this.on("input", function(msg) {
+ if (smtpTransport) {
+ node.status({fill:"blue",shape:"dot",text:"sending"});
+ var payload = RED.util.ensureString(msg.payload);
+ smtpTransport.sendMail({
+ from: node.userid, // sender address
+ to: msg.to || node.name, // comma separated list of addressees
+ subject: msg.topic, // subject line
+ text: payload // plaintext body
+ }, function(error, info) {
+ if (error) {
+ node.error(error);
+ node.status({fill:"red",shape:"ring",text:"send failed"});
+ } else {
+ node.log("Message sent: " + info.response);
+ node.status({});
+ }
+ });
+ }
+ else { node.warn("No Email credentials found. See info panel."); }
+ });
+ }
+ RED.nodes.registerType("e-mail",EmailNode,{
+ credentials: {
+ userid: {type:"text"},
+ password: {type: "password"},
+ global: { type:"boolean"}
+ }
+ });
+
+ function EmailInNode(n) {
+ RED.nodes.createNode(this,n);
+ this.name = n.name;
+ this.repeat = n.repeat * 1000 || 300000;
+ this.inserver = n.server || globalkeys.server || "imap.gmail.com";
+ this.inport = n.port || globalkeys.port || "993";
+ var flag = false;
+
+ if (this.credentials && this.credentials.hasOwnProperty("userid")) {
+ this.userid = this.credentials.userid;
+ } else {
+ if (globalkeys) {
+ this.userid = globalkeys.user;
+ flag = true;
+ } else {
+ this.error("No e-mail userid set");
+ }
+ }
+ if (this.credentials && this.credentials.hasOwnProperty("password")) {
+ this.password = this.credentials.password;
+ } else {
+ if (globalkeys) {
+ this.password = globalkeys.pass;
+ flag = true;
+ } else {
+ this.error("No e-mail password set");
+ }
+ }
+ if (flag) {
+ RED.nodes.addCredentials(n.id,{userid:this.userid, password:this.password, global:true});
+ }
+
+ var node = this;
+ this.interval_id = null;
+ var oldmail = {};
+
+ var imap = new Imap({
+ user: node.userid,
+ password: node.password,
+ host: node.inserver,
+ port: node.inport,
+ tls: true,
+ tlsOptions: { rejectUnauthorized: false },
+ connTimeout: node.repeat,
+ authTimeout: node.repeat
+ });
+
+ if (!isNaN(this.repeat) && this.repeat > 0) {
+ node.log("repeat = "+this.repeat);
+ this.interval_id = setInterval( function() {
+ node.emit("input",{});
+ }, this.repeat );
+ }
+
+ this.on("input", function(msg) {
+ imap.once('ready', function() {
+ node.status({fill:"blue",shape:"dot",text:"fetching"});
+ var pay = {};
+ imap.openBox('INBOX', true, function(err, box) {
+ if (box.messages.total > 0) {
+ var f = imap.seq.fetch(box.messages.total + ':*', { bodies: ['HEADER.FIELDS (FROM SUBJECT DATE)','TEXT'] });
+ f.on('message', function(msg, seqno) {
+ node.log('message: #'+ seqno);
+ var prefix = '(#' + seqno + ') ';
+ msg.on('body', function(stream, info) {
+ var buffer = '';
+ stream.on('data', function(chunk) {
+ buffer += chunk.toString('utf8');
+ });
+ stream.on('end', function() {
+ if (info.which !== 'TEXT') {
+ pay.from = Imap.parseHeader(buffer).from[0];
+ pay.topic = Imap.parseHeader(buffer).subject[0];
+ pay.date = Imap.parseHeader(buffer).date[0];
+ } else {
+ var parts = buffer.split("Content-Type");
+ for (var p = 0; p < parts.length; p++) {
+ if (parts[p].indexOf("text/plain") >= 0) {
+ pay.payload = parts[p].split("\n").slice(1,-2).join("\n").trim();
+ }
+ if (parts[p].indexOf("text/html") >= 0) {
+ pay.html = parts[p].split("\n").slice(1,-2).join("\n").trim();
+ }
+ }
+ //pay.body = buffer;
+ }
+ });
+ });
+ msg.on('end', function() {
+ //node.log('Finished: '+prefix);
+ });
+ });
+ f.on('error', function(err) {
+ node.warn('fetch error: ' + err);
+ node.status({fill:"red",shape:"ring",text:"fetch error"});
+ });
+ f.on('end', function() {
+ if (JSON.stringify(pay) !== oldmail) {
+ node.send(pay);
+ oldmail = JSON.stringify(pay);
+ node.log('received new email: '+pay.topic);
+ }
+ else { node.log('duplicate not sent: '+pay.topic); }
+ //node.status({fill:"green",shape:"dot",text:"ok"});
+ node.status({});
+ imap.end();
+ });
+ }
+ else {
+ node.log("you have achieved inbox zero");
+ //node.status({fill:"green",shape:"dot",text:"ok"});
+ node.status({});
+ imap.end();
+ }
+ });
+ });
+ node.status({fill:"grey",shape:"dot",text:"connecting"});
+ imap.connect();
+ });
+
+ imap.on('error', function(err) {
+ node.log(err);
+ node.status({fill:"red",shape:"ring",text:"connect error"});
+ });
+
+ this.on("error", function(err) {
+ node.log("error: ",err);
+ });
+
+ this.on("close", function() {
+ if (this.interval_id != null) {
+ clearInterval(this.interval_id);
+ }
+ if (imap) { imap.destroy(); }
+ });
+
+ node.emit("input",{});
+ }
+ RED.nodes.registerType("e-mail in",EmailInNode,{
+ credentials: {
+ userid: {type:"text"},
+ password: {type: "password"},
+ global: { type:"boolean"}
+ }
+ });
+}
diff --git a/dgbuilder/core_nodes/social/91-irc.html b/dgbuilder/core_nodes/social/91-irc.html
new file mode 100644
index 00000000..11112374
--- /dev/null
+++ b/dgbuilder/core_nodes/social/91-irc.html
@@ -0,0 +1,206 @@
+<!--
+ 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="irc in">
+ <div class="form-row">
+ <label for="node-input-ircserver"><i class="fa fa-globe"></i> IRC Server</label>
+ <input type="text" id="node-input-ircserver">
+ </div>
+ <div class="form-row">
+ <label for="node-input-channel"><i class="fa fa-random"></i> Channel</label>
+ <input type="text" id="node-input-channel" placeholder="#nodered">
+ </div>
+ <div class="form-row">
+ <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
+ <input type="text" id="node-input-name" placeholder="Name">
+ </div>
+ <div class="form-tips">The channel to join must start with a # (as per normal irc rules...)<br/>
+ You may join multiple channels by comma separating a list - #chan1,#chan2,etc.</div>
+</script>
+
+<script type="text/x-red" data-help-name="irc in">
+ <p>Connects to a channel on an IRC server.</p>
+ <p>You may join multiple channels by comma separating a list - #chan1,#chan2,#etc.</p>
+ <p>Any messages on that channel will appear on the <code>msg.payload</code> at the output,
+ while <code>msg.topic</code> will contain who it is from.
+ <code>msg.to</code> contains either the name of the channel or PRIV in the case of a pm.</p>
+ <p>The second output provides a <code>msg.payload</code> that has any status messages such as joins, parts, kicks etc.</p>
+ <p>The type of the status message is set as <code>msg.payload.type</code>.</p>
+ <p>The possible status types are: <br />
+ <table border="1" cellpadding="1" cellspacing="1">
+ <thead>
+ <tr>
+ <th scope="col">Type</th>
+ <th scope="col">Description</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>message</td>
+ <td>message is sent into the channel</td>
+ </tr>
+ <tr>
+ <td>pm</td>
+ <td>private message to the bot</td>
+ </tr>
+ <tr>
+ <td>join</td>
+ <td>a user joined the channel (also triggered when the bot joins a channel)</td>
+ </tr>
+ <tr>
+ <td>invite</td>
+ <td>the bot is being invited to a channel</td>
+ </tr>
+ <tr>
+ <td>part</td>
+ <td>a user leaves a channel</td>
+ </tr>
+ <tr>
+ <td>quit</td>
+ <td>a user quits a channel</td>
+ </tr>
+ <tr>
+ <td>kick</td>
+ <td>a user is kicked from a channel</td>
+ </tr>
+ <tr>
+ <td>names</td>
+ <td>retrieves the list of users when the bot joins a channel</td>
+ </tr>
+ </tbody>
+</table>
+</p>
+</script>
+
+<script type="text/javascript">
+ RED.nodes.registerType('irc in',{
+ category: 'social-input',
+ defaults: {
+ name: {value:""},
+ ircserver: {type:"irc-server", required:true},
+ channel: {value:"",required:true,validate:RED.validators.regex(/^#/)}
+ },
+ color:"Silver",
+ inputs:0,
+ outputs:2,
+ icon: "hash.png",
+ label: function() {
+ var ircNode = RED.nodes.node(this.ircserver);
+ return this.name || (ircNode ? ircNode.label() : "irc");
+ },
+ labelStyle: function() {
+ return this.name?"node_label_italic":"";
+ },
+ oneditprepare: function() {
+ if ((this.ircserver !== undefined) && (this.ircserver !== "")) {
+ this.channel = this.channel || RED.nodes.node(this.ircserver).channel;
+ $("#node-input-channel").val(this.channel);
+ }
+ else { this.channel = this.channel; }
+ $("#node-input-channel").val(this.channel);
+ }
+ });
+</script>
+
+
+<script type="text/x-red" data-template-name="irc out">
+ <div class="form-row">
+ <label for="node-input-ircserver"><i class="fa fa-globe"></i> IRC Server</label>
+ <input type="text" id="node-input-ircserver">
+ </div>
+ <div class="form-row">
+ <label for="node-input-channel"><i class="fa fa-random"></i> Channel</label>
+ <input type="text" id="node-input-channel" placeholder="#nodered">
+ </div>
+ <div class="form-row">
+ <label for="node-input-sendObject"><i class="fa fa-arrows"></i> Action</label>
+ <select type="text" id="node-input-sendObject" style="display: inline-block; vertical-align: middle; width:70%;">
+ <option value="pay">Send payload to channel(s)</option>
+ <option value="true">Use msg.topic to set nickname or channel(s)</option>
+ <option value="false">Send complete msg object to channel(s)</option>
+ </select>
+ </div>
+ <div class="form-row">
+ <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
+ <input type="text" id="node-input-name" placeholder="Name">
+ </div>
+ <div class="form-tips">The channel to join must start with a # (as per normal irc rules...)<br/>
+ 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>Sends messages to a channel on an IRC server</p>
+ <p>You can send just the <code>msg.payload</code>, or the complete <code>msg</code> object to the selected channel,
+ or you can select to use <code>msg.topic</code> to send the <code>msg.payload</code> to a specific user (private message) or channel.</p>
+ <p>If multiple output channels are listed (eg. #chan1,#chan2), then the message will be sent to all of them.</p>
+ <p><b>Note:</b> you can only send to channels you have previously joined so they MUST be specified in the node - even if you then decide to use a subset in msg.topic</p>
+ <p>You may send RAW commands using <code>msg.raw</code> - This must contain an array of parameters - eg. <pre>["privmsg","#nodered","Hello world"]</pre></p>
+</script>
+
+<script type="text/javascript">
+ RED.nodes.registerType('irc out',{
+ category: 'social-output',
+ defaults: {
+ name: {value:""},
+ sendObject: {value:"pay", required:true},
+ ircserver: {type:"irc-server", required:true},
+ channel: {value:"",required:true,validate:RED.validators.regex(/^#/)}
+ },
+ color:"Silver",
+ inputs:1,
+ outputs:0,
+ icon: "hash.png",
+ align: "right",
+ label: function() {
+ return this.name || (this.ircserver ? RED.nodes.node(this.ircserver).label() : "irc");
+ },
+ labelStyle: function() {
+ return this.name?"node_label_italic":"";
+ },
+ oneditprepare: function() {
+ if ((this.ircserver !== undefined) && (this.ircserver !== "")) {
+ this.channel = this.channel || RED.nodes.node(this.ircserver).channel;
+ $("#node-input-channel").val(this.channel);
+ }
+ else { this.channel = this.channel; }
+ }
+ });
+</script>
+
+
+<script type="text/x-red" data-template-name="irc-server">
+ <div class="form-row">
+ <label for="node-config-input-server"><i class="fa fa-globe"></i> IRC Server</label>
+ <input type="text" id="node-config-input-server" placeholder="irc.freenode.net">
+ </div>
+ <div class="form-row">
+ <label for="node-config-input-nickname"><i class="fa fa-user"></i> Nickname</label>
+ <input type="text" id="node-config-input-nickname" placeholder="joe123">
+ </div>
+</script>
+
+<script type="text/javascript">
+ RED.nodes.registerType('irc-server',{
+ category: 'config',
+ defaults: {
+ server: {value:"",required:true},
+ nickname: {value:"",required:true}
+ },
+ label: function() {
+ return this.server;
+ }
+ });
+</script>
diff --git a/dgbuilder/core_nodes/social/91-irc.js b/dgbuilder/core_nodes/social/91-irc.js
new file mode 100644
index 00000000..c520e44f
--- /dev/null
+++ b/dgbuilder/core_nodes/social/91-irc.js
@@ -0,0 +1,237 @@
+/**
+ * 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.
+ **/
+
+module.exports = function(RED) {
+ "use strict";
+ var irc = require("irc");
+
+ // The Server Definition - this opens (and closes) the connection
+ function IRCServerNode(n) {
+ RED.nodes.createNode(this,n);
+ this.server = n.server;
+ this.channel = n.channel;
+ this.nickname = n.nickname;
+ this.lastseen = 0;
+ this.ircclient = null;
+ this.on("close", function() {
+ if (this.ircclient != null) {
+ this.ircclient.removeAllListeners();
+ this.ircclient.disconnect();
+ }
+ });
+ }
+ RED.nodes.registerType("irc-server",IRCServerNode);
+
+
+ // The Input Node
+ function IrcInNode(n) {
+ RED.nodes.createNode(this,n);
+ this.ircserver = n.ircserver;
+ this.serverConfig = RED.nodes.getNode(this.ircserver);
+ this.channel = n.channel || this.serverConfig.channel;
+ var node = this;
+ if (node.serverConfig.ircclient === null) {
+ node.log("Connecting to "+node.serverConfig.server);
+ node.status({fill:"grey",shape:"dot",text:"connecting"});
+ node.serverConfig.ircclient = new irc.Client(node.serverConfig.server, node.serverConfig.nickname,{autoConnect:false,retryDelay:20000});
+ node.serverConfig.ircclient.setMaxListeners(0);
+ node.serverConfig.ircclient.addListener('error', function(message) {
+ node.log(JSON.stringify(message));
+ });
+ node.serverConfig.ircclient.addListener('netError', function(message) {
+ node.log(JSON.stringify("NET "+message));
+ node.serverConfig.lastseen = Date.now();
+ });
+ node.serverConfig.ircclient.addListener('connect', function() {
+ node.serverConfig.lastseen = Date.now();
+ });
+ node.serverConfig.ircclient.addListener('ping', function(server) {
+ node.serverConfig.lastseen = Date.now();
+ //node.log("PING "+JSON.stringify(server));
+ });
+ node.recon = setInterval( function() {
+ //console.log("CHK ",(Date.now()-node.serverConfig.lastseen)/1000);
+ if ((Date.now()-node.serverConfig.lastseen) > 300000) { // if more than 5 mins since last seen
+ node.ircclient.send.apply(node.ircclient,["TIME"]); // request time to check link
+ }
+ if ((Date.now()-node.serverConfig.lastseen) > 400000) { // If more than 6.5 mins
+ node.serverConfig.ircclient.disconnect();
+ node.serverConfig.ircclient.connect();
+ node.log("reconnect"); // then retry
+ }
+ node.ircclient.send.apply(node.ircclient,["TIME"]); // request time to check link
+ }, 60000); // check every 1 min
+ }
+ else { node.status({text:""}); }
+ node.ircclient = node.serverConfig.ircclient;
+
+ node.ircclient.addListener('registered', function(message) {
+ //node.log(node.ircclient.nick+" ONLINE");
+ node.status({fill:"yellow",shape:"dot",text:"connected"});
+ node.ircclient.join( node.channel, function(data) {
+ // node.log(data+" JOINED "+node.channel);
+ node.status({fill:"green",shape:"dot",text:"joined"});
+ });
+ });
+ node.ircclient.addListener('message', function (from, to, message) {
+ //node.log(from + ' => ' + to + ' : ' + message);
+ if (~node.channel.toLowerCase().indexOf(to.toLowerCase())) {
+ var msg = { "topic":from, "from":from, "to":to, "payload":message };
+ node.send([msg,null]);
+ }
+ //else { console.log(node.channel,to); }
+ });
+ node.ircclient.addListener('pm', function(from, message) {
+ //node.log("PM => "+from + ': ' + message);
+ var msg = { "topic":from, "from":from, "to":"PRIV", "payload":message };
+ node.send([msg,null]);
+ });
+ node.ircclient.addListener('join', function(channel, who) {
+ var msg = { "payload": { "type":"join", "who":who, "channel":channel } };
+ node.send([null,msg]);
+ //node.log(who+' has joined '+channel);
+ });
+ node.ircclient.addListener('invite', function(channel, from, message) {
+ var msg = { "payload": { "type":"invite", "who":from, "channel":channel, "message":message } };
+ node.send([null,msg]);
+ //node.log(from+' sent invite to '+channel+': '+message);
+ });
+ node.ircclient.addListener('part', function(channel, who, reason) {
+ var msg = { "payload": { "type":"part", "who":who, "channel":channel, "reason":reason } };
+ node.send([null,msg]);
+ //node.log(who+' has left '+channel+': '+reason);
+ });
+ node.ircclient.addListener('quit', function(nick, reason, channels, message) {
+ var msg = { "payload": { "type":"quit", "who":nick, "channel":channels, "reason":reason } };
+ node.send([null,msg]);
+ //node.log(nick+' has quit '+channels+': '+reason);
+ });
+ node.ircclient.addListener('kick', function(channel, who, by, reason) {
+ var msg = { "payload": { "type":"kick", "who":who, "channel":channel, "by":by, "reason":reason } };
+ node.send([null,msg]);
+ //node.log(who+' was kicked from '+channel+' by '+by+': '+reason);
+ });
+ node.ircclient.addListener('names', function (channel, nicks) {
+ var msg = { "payload": { "type": "names", "channel": channel, "names": nicks} };
+ node.send([null, msg]);
+ });
+ node.ircclient.addListener('raw', function (message) { // any message means we are alive
+ node.serverConfig.lastseen = Date.now();
+ });
+ node.on("close", function() {
+ node.ircclient.removeAllListeners();
+ if (node.recon) { clearInterval(node.recon); }
+ });
+ }
+ RED.nodes.registerType("irc in",IrcInNode);
+
+
+ // The Output Node
+ function IrcOutNode(n) {
+ RED.nodes.createNode(this,n);
+ this.sendFlag = n.sendObject;
+ this.ircserver = n.ircserver;
+ this.serverConfig = RED.nodes.getNode(this.ircserver);
+ this.channel = n.channel || this.serverConfig.channel;
+ var node = this;
+ if (node.serverConfig.ircclient === null) {
+ node.log("connecting to "+node.serverConfig.server);
+ node.status({fill:"grey",shape:"dot",text:"connecting"});
+ node.serverConfig.ircclient = new irc.Client(node.serverConfig.server, node.serverConfig.nickname,{autoConnect:false,retryDelay:20000});
+ node.serverConfig.ircclient.setMaxListeners(0);
+ node.serverConfig.ircclient.addListener('error', function(message) {
+ node.log(JSON.stringify(message));
+ });
+ node.serverConfig.ircclient.addListener('netError', function(message) {
+ node.log(JSON.stringify("NET "+message));
+ node.serverConfig.lastseen = Date.now();
+ });
+ node.serverConfig.ircclient.addListener('connect', function() {
+ node.serverConfig.lastseen = Date.now();
+ });
+ node.serverConfig.ircclient.addListener('ping', function(server) {
+ node.serverConfig.lastseen = Date.now();
+ //node.log("PING "+JSON.stringify(server));
+ });
+ node.serverConfig.ircclient.addListener('raw', function (message) { // any message received means we are alive
+ if (message.commandType === "reply") { node.serverConfig.lastseen = Date.now(); }
+ });
+ node.recon = setInterval( function() {
+ //console.log("CHK ",(Date.now()-node.serverConfig.lastseen)/1000);
+ if ((Date.now()-node.serverConfig.lastseen) > 300000) { // if more than 5 mins since last seen
+ node.ircclient.send.apply(node.ircclient,["TIME"]); // request time to check link
+ }
+ if ((Date.now()-node.serverConfig.lastseen) > 400000) { // If more than 6.5 mins
+ node.serverConfig.ircclient.disconnect();
+ node.serverConfig.ircclient.connect();
+ node.log("reconnect"); // then retry
+ }
+ node.ircclient.send.apply(node.ircclient,["TIME"]); // request time to check link
+ }, 60000); // check every 1 min
+ node.serverConfig.ircclient.connect();
+ }
+ else { node.status({text:""}); }
+ node.ircclient = node.serverConfig.ircclient;
+
+ node.ircclient.addListener('registered', function(message) {
+ node.log(node.ircclient.nick+" ONLINE");
+ node.status({fill:"yellow",shape:"dot",text:"connected"});
+ node.ircclient.join( node.channel, function(data) {
+ //node.log(data+" JOINED "+node.channel);
+ node.status({fill:"green",shape:"dot",text:"joined"});
+ });
+ });
+
+ node.on("input", function(msg) {
+ if (Object.prototype.toString.call( msg.raw ) === '[object Array]') {
+ node.log("RAW command:"+msg.raw);
+ node.ircclient.send.apply(node.ircclient,msg.raw);
+ //var m = msg.raw;
+ //for (var i = 0; i < 10; i++) {
+ //if (typeof m[i] !== "string") { m[i] = ""; }
+ //m[i] = m[i].replace(/"/g, "");
+ //}
+ //node.log("RAW command:"+m);
+ //node.ircclient.send(m[0],m[1],m[2],m[3],m[4],m[5],m[6],m[7],m[8],m[9]);
+ }
+ else {
+ if (msg._topic) { delete msg._topic; }
+ var ch = node.channel.split(","); // split on , so we can send to multiple
+ if (node.sendFlag == "true") { // override channels with msg.topic
+ if ((msg.hasOwnProperty('topic'))&&(typeof msg.topic === "string")) {
+ ch = msg.topic.split(","); // split on , so we can send to multiple
+ }
+ else { node.warn("msg.topic not set"); }
+ }
+ for (var c = 0; c < ch.length; c++) {
+ if (node.sendFlag == "false") { // send whole message object to each channel
+ node.ircclient.say(ch[c], JSON.stringify(msg));
+ }
+ else { // send just the payload to each channel
+ if (typeof msg.payload === "object") { msg.payload = JSON.stringify(msg.payload); }
+ node.ircclient.say(ch[c], msg.payload);
+ }
+ }
+ }
+ });
+
+ node.on("close", function() {
+ node.ircclient.removeAllListeners();
+ if (node.recon) { clearInterval(node.recon); }
+ });
+ }
+ RED.nodes.registerType("irc out",IrcOutNode);
+}
diff --git a/dgbuilder/core_nodes/storage/28-tail.html b/dgbuilder/core_nodes/storage/28-tail.html
new file mode 100644
index 00000000..c094d064
--- /dev/null
+++ b/dgbuilder/core_nodes/storage/28-tail.html
@@ -0,0 +1,58 @@
+<!--
+ 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="tail">
+ <div class="form-row node-input-filename">
+ <label for="node-input-filename"><i class="fa fa-file"></i> Filename</label>
+ <input type="text" id="node-input-filename" placeholder="Filename">
+ </div>
+ <div class="form-row">
+ <label>&nbsp;</label>
+ <input type="checkbox" id="node-input-split" placeholder="Name" style="display: inline-block; width: auto; vertical-align: top;">
+ <label for="node-input-split" style="width: 70%;">Split lines if we see \n ?</label>
+ </div>
+ <div class="form-row">
+ <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
+ <input type="text" id="node-input-name" placeholder="Name">
+ </div>
+ <!-- <div class="form-tips">WON'T work on Windows.</div> -->
+</script>
+
+<script type="text/x-red" data-help-name="tail">
+ <p>Tails (watches for things to be added) to the configured file. (Linux/Mac ONLY)</p>
+ <p>This won't work on Windows filesystems, as it relies on the tail -F command.</p>
+</script>
+
+<script type="text/javascript">
+ RED.nodes.registerType('tail',{
+ category: 'storage-input',
+ defaults: {
+ name: {value:""},
+ split: {value:false},
+ filename: {value:"",required:true}
+ },
+ color:"BurlyWood",
+ inputs:0,
+ outputs:1,
+ icon: "file.png",
+ label: function() {
+ return this.name||this.filename;
+ },
+ labelStyle: function() {
+ return this.name?"node_label_italic":"";
+ }
+ });
+</script>
diff --git a/dgbuilder/core_nodes/storage/28-tail.js b/dgbuilder/core_nodes/storage/28-tail.js
new file mode 100644
index 00000000..89c7a639
--- /dev/null
+++ b/dgbuilder/core_nodes/storage/28-tail.js
@@ -0,0 +1,69 @@
+/**
+ * Copyright 2013, 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.
+ **/
+
+module.exports = function(RED) {
+ "use strict";
+ var spawn = require('child_process').spawn;
+ var plat = require('os').platform();
+
+ if (plat.match(/^win/)) {
+ throw "Info : Currently not supported on Windows.";
+ }
+
+ function TailNode(n) {
+ RED.nodes.createNode(this,n);
+
+ this.filename = n.filename;
+ this.split = n.split;
+ var node = this;
+
+ var err = "";
+ // TODO: rewrite to use node-tail
+ var tail = spawn("tail", ["-F", "-n", "0", this.filename]);
+ tail.stdout.on("data", function (data) {
+ if (node.split) {
+ // TODO: allow customisation of the line break - as we do elsewhere
+ var strings = data.toString().split("\n");
+ for (var s in strings) {
+ //TODO: should we really filter blanks? Is that expected?
+ if (strings[s] !== "") {
+ node.send({
+ topic: node.filename,
+ payload: strings[s]
+ });
+ }
+ }
+ }
+ else {
+ var msg = {
+ topic:node.filename,
+ payload: data.toString()
+ };
+ node.send(msg);
+ }
+ });
+
+ tail.stderr.on("data", function(data) {
+ node.warn(data.toString());
+ });
+
+ this.on("close", function() {
+ if (tail) { tail.kill(); }
+ });
+ }
+
+ RED.nodes.registerType("tail",TailNode);
+}
diff --git a/dgbuilder/core_nodes/storage/50-file.html b/dgbuilder/core_nodes/storage/50-file.html
new file mode 100644
index 00000000..5113a17d
--- /dev/null
+++ b/dgbuilder/core_nodes/storage/50-file.html
@@ -0,0 +1,110 @@
+<!--
+ Copyright 2013, 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="file">
+ <div class="form-row node-input-filename">
+ <label for="node-input-filename"><i class="fa fa-file"></i> Filename</label>
+ <input type="text" id="node-input-filename" placeholder="Filename">
+ </div>
+ <div class="form-row">
+ <label>&nbsp;</label>
+ <input type="checkbox" id="node-input-appendNewline" placeholder="Name" style="display: inline-block; width: auto; vertical-align: top;">
+ <label for="node-input-appendNewline" style="width: 70%;">Append newline ?</label>
+ </div>
+ <div class="form-row">
+ <label>&nbsp;</label>
+ <input type="checkbox" id="node-input-overwriteFile" placeholder="Name" style="display: inline-block; width: auto; vertical-align: top;">
+ <label for="node-input-overwriteFile" style="width: 70%;">Overwrite complete file ?</label>
+ </div>
+ <div class="form-row">
+ <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
+ <input type="text" id="node-input-name" placeholder="Name">
+ </div>
+</script>
+
+<script type="text/x-red" data-help-name="file">
+ <p>Writes <b>msg.payload</b> to the file specified, e.g. to create a log.</p>
+ <p>The filename can be overridden by the <b>msg.filename</b> property of the incoming message.</p>
+ <p>A newline is added to every message. But this can be turned off if required, for example, to allow binary files to be written.</p>
+ <p>The default behaviour is to append to the file. This can be changed to overwrite the file each time, for example if you want to output a "static" web page or report.</p>
+ <p>If a <b>msg.delete</b> property exists then the file will be deleted instead.</p>
+</script>
+
+<script type="text/x-red" data-template-name="file in">
+ <div class="form-row">
+ <label for="node-input-filename"><i class="fa fa-file"></i> Filename</label>
+ <input type="text" id="node-input-filename" placeholder="Filename">
+ </div>
+ <div class="form-row">
+ <label for="node-input-format"><i class="fa fa-sign-out"></i> Output as</label>
+ <select id="node-input-format">
+ <option value="utf8">a utf8 string</option>
+ <option value="">a Buffer</option>
+ </select>
+ </div>
+ <div class="form-row">
+ <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
+ <input type="text" id="node-input-name" placeholder="Name">
+ </div>
+</script>
+
+<script type="text/x-red" data-help-name="file in">
+ <p>Reads the specified file and sends the content as <b>msg.payload</b>, and the filename as <b>msg.filename</b>.</p>
+ <p>The filename can be overridden by the <b>msg.filename</b> property of the incoming message.</p>
+</script>
+
+<script type="text/javascript">
+ RED.nodes.registerType('file',{
+ category: 'storage-output',
+ defaults: {
+ name: {value:""},
+ filename: {value:""},
+ appendNewline: {value:true},
+ overwriteFile: {value:false}
+ },
+ color:"BurlyWood",
+ inputs:1,
+ outputs:0,
+ icon: "file.png",
+ align: "right",
+ label: function() {
+ return this.name||this.filename;
+ },
+ labelStyle: function() {
+ return this.name?"node_label_italic":"";
+ }
+ });
+
+ RED.nodes.registerType('file in',{
+ category: 'storage-input',
+ defaults: {
+ name: {value:""},
+ filename: {value:""},
+ format: {value:"utf8"},
+ },
+ color:"BurlyWood",
+ inputs:1,
+ outputs:1,
+ icon: "file.png",
+ label: function() {
+ return this.name||this.filename;
+ },
+ labelStyle: function() {
+ return this.name?"node_label_italic":"";
+ }
+ });
+
+</script>
diff --git a/dgbuilder/core_nodes/storage/50-file.js b/dgbuilder/core_nodes/storage/50-file.js
new file mode 100644
index 00000000..d6cc4410
--- /dev/null
+++ b/dgbuilder/core_nodes/storage/50-file.js
@@ -0,0 +1,93 @@
+/**
+ * Copyright 2013, 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.
+ **/
+
+module.exports = function(RED) {
+ "use strict";
+ var fs = require("fs");
+
+ function FileNode(n) {
+ RED.nodes.createNode(this,n);
+ this.filename = n.filename || "";
+ this.appendNewline = n.appendNewline;
+ this.overwriteFile = n.overwriteFile;
+ var node = this;
+ this.on("input",function(msg) {
+ var filename = msg.filename || this.filename;
+ if (filename === "") {
+ node.warn('No filename specified');
+ } else if (msg.hasOwnProperty('delete')) {
+ fs.unlink(filename, function (err) {
+ if (err) { node.warn('Failed to delete file : '+err); }
+ //console.log('Deleted file",filename);
+ });
+ } else if (typeof msg.payload != "undefined") {
+ var data = msg.payload;
+ if (typeof data === "object") {
+ if (!Buffer.isBuffer(data)) {
+ data = JSON.stringify(data);
+ }
+ }
+ if (typeof data === "boolean") { data = data.toString(); }
+ if ((this.appendNewline)&&(!Buffer.isBuffer(data))) { data += "\n"; }
+ if (this.overwriteFile) {
+ // using "binary" not {encoding:"binary"} to be 0.8 compatible for a while
+ fs.writeFile(filename, data, "binary", function (err) {
+ if (err) { node.warn('Failed to write to file : '+err); }
+ //console.log('Message written to file',filename);
+ });
+ }
+ else {
+ // using "binary" not {encoding:"binary"} to be 0.8 compatible for a while
+ fs.appendFile(filename, data, "binary", function (err) {
+ if (err) { node.warn('Failed to append to file : '+err); }
+ //console.log('Message appended to file',filename);
+ });
+ }
+ }
+ });
+ }
+ RED.nodes.registerType("file",FileNode);
+
+ function FileInNode(n) {
+ RED.nodes.createNode(this,n);
+
+ this.filename = n.filename || "";
+ this.format = n.format;
+ var node = this;
+ var options = {};
+ if (this.format) {
+ options['encoding'] = this.format;
+ }
+ this.on("input",function(msg) {
+ var filename = msg.filename || this.filename;
+ if (filename === "") {
+ node.warn('No filename specified');
+ } else {
+ fs.readFile(filename,options,function(err,data) {
+ if (err) {
+ node.warn(err);
+ msg.error = err;
+ } else {
+ msg.filename = filename;
+ msg.payload = data;
+ }
+ node.send(msg);
+ });
+ }
+ });
+ }
+ RED.nodes.registerType("file in",FileInNode);
+}
diff --git a/dgbuilder/core_nodes/storage/65-redisout.html b/dgbuilder/core_nodes/storage/65-redisout.html
new file mode 100644
index 00000000..9000dfd6
--- /dev/null
+++ b/dgbuilder/core_nodes/storage/65-redisout.html
@@ -0,0 +1,105 @@
+<!--
+ 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="redis out">
+ <div class="form-row node-input-hostname">
+ <label for="node-input-hostname"><i class="fa fa-bookmark"></i> Host</label>
+ <input class="input-append-left" type="text" id="node-input-hostname" placeholder="127.0.0.1" style="width: 40%;" ><button id="node-input-hostname-lookup" class="btn input-append-right"><span class="caret"></span></button>
+ <label for="node-input-port" style="margin-left: 10px; width: 35px; "> Port</label>
+ <input type="text" id="node-input-port" placeholder="6379" style="width:45px">
+ </div>
+ <div class="form-row">
+ <label for="node-input-key"><i class="fa fa-key"></i> Key</label>
+ <input type="text" id="node-input-key" placeholder="Redis Key">
+ </div>
+ <div class="form-row">
+ <label for="node-input-type"><i class="fa fa-th"></i> Type</label>
+ <select type="text" id="node-input-structtype" style="width: 150px;">
+ <option value="string">String</option>
+ <option value="hash">Hash</option>
+ <option value="set">Set</option>
+ <option value="list">List</option>
+ </select>
+ </div>
+ <div class="form-row">
+ <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
+ <input type="text" id="node-input-name" placeholder="Name">
+ </div>
+ <div class="form-tips">
+ If key is blank, the topic will be used as the key.<br>
+ If type is hash, payload should be field=value.
+ </div>
+</script>
+
+<script type="text/x-red" data-help-name="redis out">
+ <p>A Redis output node. Options include Hash, Set, List and String.</p>
+ <p>To run this you need a local Redis server running. For details see <a href="http://redis.io/" target="_new">the Redis site</a>.</p>
+</script>
+
+<script type="text/javascript">
+ RED.nodes.registerType('redis out',{
+ category: 'storage-output',
+ color:"#ffaaaa",
+ defaults: {
+ hostname: { value:"127.0.0.1",required:true},
+ port: { value: 6379,required:true},
+ name: {value:""},
+ key: {value:""},
+ structtype: {value:"",required:true}
+ },
+ inputs:1,
+ outputs:0,
+ icon: "redis.png",
+ align: "right",
+ label: function() {
+ return this.name||this.key+" ("+this.structtype+")";
+ },
+ oneditprepare: function() {
+ var availableServers = [];
+ var matchedServers = {};
+ RED.nodes.eachNode(function(node) {
+ if (node.type == "redis out" && node.hostname && node.port && !matchedServers[node.hostname+":"+node.port]) {
+ var label = node.hostname+":"+node.port;
+ matchedServers[label] = true;
+ availableServers.push({
+ label:label,
+ value:node.hostname,
+ port:node.port
+ });
+ }
+ });
+ $( "#node-input-hostname" ).autocomplete({
+ minLength: 0,
+ source: availableServers,
+ select: function( event, ui ) {
+ $("#node-input-port").val(ui.item.port);
+ }
+ });
+ var tt = this;
+ tt._acOpen = false;
+ $( "#node-input-hostname" ).on( "autocompleteclose", function( event, ui ) { tt._acOpen = false;} );
+ $( "#node-input-hostname-lookup" ).click(function(e) {
+ if (tt._acOpen) {
+ $( "#node-input-hostname" ).autocomplete( "close");
+ } else {
+ $( "#node-input-hostname" ).autocomplete( "search", "" );
+ }
+ tt._acOpen = !tt._acOpen;
+ e.preventDefault();
+ });
+ }
+ });
+</script>
diff --git a/dgbuilder/core_nodes/storage/65-redisout.js b/dgbuilder/core_nodes/storage/65-redisout.js
new file mode 100644
index 00000000..907e2a55
--- /dev/null
+++ b/dgbuilder/core_nodes/storage/65-redisout.js
@@ -0,0 +1,107 @@
+/**
+ * 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.
+ **/
+
+module.exports = function(RED) {
+ "use strict";
+ var util = require("util");
+ var redis = require("redis");
+
+ var hashFieldRE = /^([^=]+)=(.*)$/;
+
+ var redisConnectionPool = function() {
+ var connections = {};
+ var obj = {
+ get: function(host,port) {
+ var id = host+":"+port;
+ if (!connections[id]) {
+ connections[id] = redis.createClient(port,host);
+ connections[id].on("error",function(err) {
+ util.log("[redis] "+err);
+ });
+ connections[id].on("connect",function() {
+ util.log("[redis] connected to "+host+":"+port);
+ });
+ connections[id]._id = id;
+ connections[id]._nodeCount = 0;
+ }
+ connections[id]._nodeCount += 1;
+ return connections[id];
+ },
+ close: function(connection) {
+ connection._nodeCount -= 1;
+ if (connection._nodeCount === 0) {
+ if (connection) {
+ clearTimeout(connection.retry_timer);
+ connection.end();
+ }
+ delete connections[connection._id];
+ }
+ }
+ };
+ return obj;
+ }();
+
+
+ function RedisOutNode(n) {
+ RED.nodes.createNode(this,n);
+ this.port = n.port||"6379";
+ this.hostname = n.hostname||"127.0.0.1";
+ this.key = n.key;
+ this.structtype = n.structtype;
+
+ this.client = redisConnectionPool.get(this.hostname,this.port);
+
+ if (this.client.connected) {
+ this.status({fill:"green",shape:"dot",text:"connected"});
+ } else {
+ this.status({fill:"red",shape:"ring",text:"disconnected"},true);
+ }
+
+ var node = this;
+ this.client.on("end", function() {
+ node.status({fill:"red",shape:"ring",text:"disconnected"});
+ });
+ this.client.on("connect", function() {
+ node.status({fill:"green",shape:"dot",text:"connected"});
+ });
+
+ this.on("input", function(msg) {
+ var k = this.key || msg.topic;
+ if (k) {
+ if (this.structtype == "string") {
+ this.client.set(k,RED.util.ensureString(msg.payload));
+ } else if (this.structtype == "hash") {
+ var r = hashFieldRE.exec(msg.payload);
+ if (r) {
+ this.client.hset(k,r[1],r[2]);
+ } else {
+ this.warn("Invalid payload for redis hash");
+ }
+ } else if (this.structtype == "set") {
+ this.client.sadd(k,msg.payload);
+ } else if (this.structtype == "list") {
+ this.client.rpush(k,msg.payload);
+ }
+ } else {
+ this.warn("No key or topic set");
+ }
+ });
+ this.on("close", function() {
+ redisConnectionPool.close(node.client);
+ });
+ }
+ RED.nodes.registerType("redis out",RedisOutNode);
+}
diff --git a/dgbuilder/core_nodes/storage/66-mongodb.html b/dgbuilder/core_nodes/storage/66-mongodb.html
new file mode 100644
index 00000000..81c56389
--- /dev/null
+++ b/dgbuilder/core_nodes/storage/66-mongodb.html
@@ -0,0 +1,231 @@
+<!--
+ Copyright 2013,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="mongodb">
+ <div class="form-row">
+ <label for="node-config-input-hostname"><i class="fa fa-bookmark"></i> Host</label>
+ <input class="input-append-left" type="text" id="node-config-input-hostname" placeholder="localhost" style="width: 40%;" >
+ <label for="node-config-input-port" style="margin-left: 10px; width: 35px; "> Port</label>
+ <input type="text" id="node-config-input-port" placeholder="27017" style="width:45px">
+ </div>
+ <div class="form-row">
+ <label for="node-config-input-db"><i class="fa fa-database"></i> Database</label>
+ <input type="text" id="node-config-input-db" placeholder="test">
+ </div>
+ <div class="form-row">
+ <label for="node-config-input-user"><i class="fa fa-user"></i> Username</label>
+ <input type="text" id="node-config-input-user">
+ </div>
+ <div class="form-row">
+ <label for="node-config-input-password"><i class="fa fa-lock"></i> Password</label>
+ <input type="password" id="node-config-input-password">
+ </div>
+ <div class="form-row">
+ <label for="node-config-input-name"><i class="fa fa-tag"></i> Name</label>
+ <input type="text" id="node-config-input-name" placeholder="Name">
+ </div>
+</script>
+
+<script type="text/javascript">
+ RED.nodes.registerType('mongodb', {
+ category: 'config',
+ color: "rgb(218, 196, 180)",
+ defaults: {
+ hostname: {value: "127.0.0.1", required: true},
+ port: {value: 27017, required: true},
+ db: {value: "", required: true},
+ name: {value: ""}
+ },
+ credentials: {
+ user: {type: "text"},
+ password: {type: "password"}
+ },
+ label: function() {
+ return this.name || this.hostname + ":" + this.port + "/" + this.db;
+ }
+ });
+</script>
+
+
+<script type="text/x-red" data-template-name="mongodb out">
+ <div class="form-row">
+ <label for="node-input-mongodb"><i class="fa fa-bookmark"></i> Server</label>
+ <input type="text" id="node-input-mongodb">
+ </div>
+ <div class="form-row">
+ <label for="node-input-collection"><i class="fa fa-briefcase"></i> Collection</label>
+ <input type="text" id="node-input-collection" placeholder="collection">
+ </div>
+ <div class="form-row">
+ <label for="node-input-operation"><i class="fa fa-wrench"></i> Operation</label>
+ <select type="text" id="node-input-operation" style="display: inline-block; vertical-align: top;">
+ <option value="store">save</option>
+ <option value="insert">insert</option>
+ <option value="update">update</option>
+ <option value="delete">remove</option>
+ </select>
+ </div>
+ <div class="form-row node-input-payonly">
+ <label>&nbsp;</label>
+ <input type="checkbox" id="node-input-payonly" placeholder="Only" style="display: inline-block; width: auto; vertical-align: top;">
+ <label for="node-input-payonly" style="width: 70%;">Only store msg.payload object</label>
+ </div>
+ <div class="form-row node-input-upsert">
+ <label>&nbsp;</label>
+ <input type="checkbox" id="node-input-upsert" placeholder="Only" style="display: inline-block; width: auto; vertical-align: top;">
+ <label for="node-input-upsert" style="width: 70%;">Create a new document if no match found</label>
+ </div>
+ <div class="form-row node-input-multi">
+ <label>&nbsp;</label>
+ <input type="checkbox" id="node-input-multi" placeholder="Only" style="display: inline-block; width: auto; vertical-align: top;;">
+ <label for="node-input-multi" style="width: 70%;">Update all matching documents</label>
+ </div>
+ <div class="form-row">
+ <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
+ <input type="text" id="node-input-name" placeholder="Name">
+ </div>
+ <div class="form-tips" id="node-warning" style="display: none"><b> Tip:</b> If no collection is set, ensure <b>msg.collection</b> will contain the collection name
+ </div>
+</script>
+
+<script type="text/x-red" data-help-name="mongodb out">
+ <p>A simple MongoDB output node. Can save, insert, update and remove objects from a chosen collection.</p>
+ <p>Save will update an existing object or insert a new object if one does not already exist.</p>
+ <p>Insert will insert a new object.</p>
+ <p>Save and insert either store <b>msg</b> or <b>msg.payload</b>.</p>
+ <p>Update will modify an existing object or objects. The query to select objects to update uses <b>msg.query</b> and the update to the element uses <b>msg.payload</b>.</p>
+ <p>Update can add a object if it does not exist or update multiple objects.</p>
+ <p>Remove will remove objects that match the query passed in on <b>msg.payload</b>. A blank query will delete <i>all of the objects</i> in the collection.</p>
+ <p>You can either set the collection method in the node config or on <b>msg.collection</b>. Setting it in the node will override <b>msg.collection</b>.</p>
+ <p>By default MongoDB creates an <i>_id</i> property as the primary key - so repeated injections of the same <b>msg</b> will result in many database entries.</p>
+ <p>If this is NOT the desired behaviour - ie. you want repeated entries to overwrite, then you must set the <b>msg._id</b> property to be a constant by the use of a previous function node.</p>
+ <p>This could be a unique constant or you could create one based on some other msg property.</p>
+ <p>Currently we do not limit or cap the collection size at all... this may well change.</p>
+</script>
+
+<script type="text/javascript">
+
+ function oneditprepare() {
+ $("#node-input-operation").change(function () {
+ var id = $("#node-input-operation option:selected").val();
+
+ if (id === "update") {
+ $(".node-input-payonly").hide();
+ $(".node-input-upsert, .node-input-multi").show();
+ } else if (id === "delete") {
+ $(".node-input-payonly, .node-input-upsert, .node-input-multi").hide();
+ } else {
+ $(".node-input-payonly").show();
+ $(".node-input-upsert, .node-input-multi").hide();
+ }
+ });
+
+ $("#node-input-collection").change(function () {
+ if($("#node-input-collection").val() === "") {
+ $("#node-warning").show();
+ } else {
+ $("#node-warning").hide();
+ }
+ });
+ }
+
+ RED.nodes.registerType('mongodb out', {
+ category: 'storage-output',
+ color: "rgb(218, 196, 180)",
+ defaults: {
+ mongodb: {type: "mongodb", required: true},
+ name: {value: ""},
+ collection: {value: ""},
+ payonly: {value: false},
+ upsert: {value: false},
+ multi: {value: false},
+ operation: {value: "store"}
+ },
+ inputs: 1,
+ outputs: 0,
+ icon: "mongodb.png",
+ align: "right",
+ label: function() {
+ var mongoNode = RED.nodes.node(this.mongodb);
+ return this.name || (mongoNode ? mongoNode.label() + " " + this.collection: "mongodb");
+ },
+ labelStyle: function() {
+ return this.name ? "node_label_italic" : "";
+ },
+ oneditprepare: oneditprepare
+ });
+</script>
+
+
+<script type="text/x-red" data-template-name="mongodb in">
+ <div class="form-row">
+ <label for="node-input-mongodb"><i class="fa fa-bookmark"></i> Server</label>
+ <input type="text" id="node-input-mongodb">
+ </div>
+ <div class="form-row">
+ <label for="node-input-collection"><i class="fa fa-briefcase"></i> Collection</label>
+ <input type="text" id="node-input-collection" placeholder="collection">
+ </div>
+ <div class="form-row">
+ <label for="node-input-operation"><i class="fa fa-wrench"></i> Operation</label>
+ <select type="text" id="node-input-operation" style="display: inline-block; vertical-align: top;">
+ <option value="find">find</option>
+ <option value="count">count</option>
+ <option value="aggregate">aggregate</option>
+ </select>
+ </div>
+ <div class="form-row">
+ <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
+ <input type="text" id="node-input-name" placeholder="Name">
+ </div>
+ <div class="form-tips" id="node-warning" style="display: none"><b> Tip:</b> If no collection is set, ensure <b>msg.collection</b> will contain the collection name
+ </div>
+</script>
+
+<script type="text/x-red" data-help-name="mongodb in">
+ <p>Calls a MongoDB collection method based on the selected operator.</p>
+ <p>Find queries a collection using the <b>msg.payload</b> as the query statement as per the .find() function. Optionally, you may also (via a function) set a <b>msg.projection</b> object to constrain the returned fields, a <b>msg.sort</b> object and a <b>msg.limit</b> object.</p>
+ <p>Count returns a count of the number of documents in a collection or matching a query using the <b>msg.payload</b> as the query statement.</p>
+ <p>Aggregate provides access to the aggregation pipeline using the <b>msg.payload</b> as the pipeline array.</p>
+ <p>You can override the collection the method is performed on by setting <b>msg.collection</b> to the desired collection name.</p>
+ <p>See the <a href="http://docs.mongodb.org/manual/reference/method/db.collection.find/" target="new"><i>MongoDB collection methods docs</i></a> for examples.</p>
+ <p>The result is returned in <b>msg.payload</b>.</p>
+</script>
+
+<script type="text/javascript">
+
+ RED.nodes.registerType('mongodb in', {
+ category: 'storage-input',
+ color: "rgb(218, 196, 180)",
+ defaults: {
+ mongodb: {type: "mongodb", required: true},
+ name: {value: ""},
+ collection: {value: ""},
+ operation: {value: "find"}
+ },
+ inputs: 1,
+ outputs: 1,
+ icon: "mongodb.png",
+ label: function() {
+ var mongoNode = RED.nodes.node(this.mongodb);
+ return this.name || (mongoNode ? mongoNode.label() + " " + this.collection: "mongodb");
+ },
+ labelStyle: function() {
+ return this.name ? "node_label_italic" : "";
+ },
+ oneditprepare: oneditprepare
+ });
+</script>
diff --git a/dgbuilder/core_nodes/storage/66-mongodb.js b/dgbuilder/core_nodes/storage/66-mongodb.js
new file mode 100644
index 00000000..3a71407c
--- /dev/null
+++ b/dgbuilder/core_nodes/storage/66-mongodb.js
@@ -0,0 +1,233 @@
+/**
+ * Copyright 2013,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.
+ **/
+
+module.exports = function(RED) {
+ "use strict";
+ var mongo = require('mongodb');
+ var MongoClient = mongo.MongoClient;
+
+ function MongoNode(n) {
+ RED.nodes.createNode(this,n);
+ this.hostname = n.hostname;
+ this.port = n.port;
+ this.db = n.db;
+ this.name = n.name;
+
+ var url = "mongodb://";
+ if (this.credentials && this.credentials.user && this.credentials.password) {
+ url += this.credentials.user+":"+this.credentials.password+"@";
+ }
+ url += this.hostname+":"+this.port+"/"+this.db;
+
+ this.url = url;
+ }
+
+ RED.nodes.registerType("mongodb",MongoNode,{
+ credentials: {
+ user: {type:"text"},
+ password: {type: "password"}
+ }
+ });
+
+ function ensureValidSelectorObject(selector) {
+ if (selector != null && (typeof selector != 'object' || Buffer.isBuffer(selector))) {
+ return {};
+ }
+ return selector;
+ }
+
+
+ function MongoOutNode(n) {
+ RED.nodes.createNode(this,n);
+ this.collection = n.collection;
+ this.mongodb = n.mongodb;
+ this.payonly = n.payonly || false;
+ this.upsert = n.upsert || false;
+ this.multi = n.multi || false;
+ this.operation = n.operation;
+ this.mongoConfig = RED.nodes.getNode(this.mongodb);
+
+ if (this.mongoConfig) {
+ var node = this;
+ MongoClient.connect(this.mongoConfig.url, function(err, db) {
+ if (err) {
+ node.error(err);
+ } else {
+ node.clientDb = db;
+ var coll;
+ if (node.collection) {
+ coll = db.collection(node.collection);
+ }
+ node.on("input",function(msg) {
+ if (!coll) {
+ if (msg.collection) {
+ coll = db.collection(msg.collection);
+ } else {
+ node.error("No collection defined");
+ return;
+ }
+ }
+ delete msg._topic;
+ delete msg.collection;
+ if (node.operation === "store") {
+ if (node.payonly) {
+ if (typeof msg.payload !== "object") {
+ msg.payload = {"payload": msg.payload};
+ }
+ coll.save(msg.payload,function(err, item) {
+ if (err) {
+ node.error(err);
+ }
+ });
+ } else {
+ coll.save(msg,function(err, item) {
+ if (err) {
+ node.error(err);
+ }
+ });
+ }
+ } else if (node.operation === "insert") {
+ if (node.payonly) {
+ if (typeof msg.payload !== "object") {
+ msg.payload = {"payload": msg.payload};
+ }
+ coll.insert(msg.payload, function(err, item) {
+ if (err) {
+ node.error(err);
+ }
+ });
+ } else {
+ coll.insert(msg, function(err,item) {
+ if (err) {
+ node.error(err);
+ }
+ });
+ }
+ } else if (node.operation === "update") {
+ if (typeof msg.payload !== "object") {
+ msg.payload = {"payload": msg.payload};
+ }
+ var query = msg.query || {};
+ var payload = msg.payload || {};
+ var options = {
+ upsert: node.upsert,
+ multi: node.multi
+ };
+
+ coll.update(query, payload, options, function(err, item) {
+ if (err) {
+ node.error(err + " " + payload);
+ }
+ });
+ } else if (node.operation === "delete") {
+ coll.remove(msg.payload, function(err, items) {
+ if (err) {
+ node.error(err);
+ }
+ });
+ }
+ });
+ }
+ });
+ } else {
+ this.error("missing mongodb configuration");
+ }
+
+ this.on("close", function() {
+ if (this.clientDb) {
+ this.clientDb.close();
+ }
+ });
+ }
+ RED.nodes.registerType("mongodb out",MongoOutNode);
+
+ function MongoInNode(n) {
+ RED.nodes.createNode(this,n);
+ this.collection = n.collection;
+ this.mongodb = n.mongodb;
+ this.operation = n.operation || "find";
+ this.mongoConfig = RED.nodes.getNode(this.mongodb);
+
+ if (this.mongoConfig) {
+ var node = this;
+ MongoClient.connect(this.mongoConfig.url, function(err,db) {
+ if (err) {
+ node.error(err);
+ } else {
+ node.clientDb = db;
+ var coll;
+ if (node.collection) {
+ coll = db.collection(node.collection);
+ }
+ node.on("input", function(msg) {
+ if (!coll) {
+ if (msg.collection) {
+ coll = db.collection(msg.collection);
+ } else {
+ node.error("No collection defined");
+ return;
+ }
+ }
+ if (node.operation === "find") {
+ msg.projection = msg.projection || {};
+ var selector = ensureValidSelectorObject(msg.payload);
+ coll.find(selector,msg.projection).sort(msg.sort).limit(msg.limit).toArray(function(err, items) {
+ if (err) {
+ node.error(err);
+ } else {
+ msg.payload = items;
+ delete msg.projection;
+ delete msg.sort;
+ delete msg.limit;
+ node.send(msg);
+ }
+ });
+ } else if (node.operation === "count") {
+ var selector = ensureValidSelectorObject(msg.payload);
+ coll.count(selector, function(err, count) {
+ if (err) {
+ node.error(err);
+ } else {
+ msg.payload = count;
+ node.send(msg);
+ }
+ });
+ } else if (node.operation === "aggregate") {
+ msg.payload = (msg.payload instanceof Array) ? msg.payload : [];
+ coll.aggregate(msg.payload, function(err, result) {
+ if (err) {
+ node.error(err);
+ } else {
+ msg.payload = result;
+ node.send(msg);
+ }
+ });
+ }
+ });
+ }
+ });
+ } else {
+ this.error("missing mongodb configuration");
+ }
+
+ this.on("close", function() {
+ if (this.clientDb) {
+ this.clientDb.close();
+ }
+ });
+ }
+ RED.nodes.registerType("mongodb in",MongoInNode);
+}