From 8a8b9bf7d82c0c4c375f0125922b6e9ab5b7ca3f Mon Sep 17 00:00:00 2001
From: Wolfgang Nagele
Date: Mon, 9 Dec 2013 21:45:15 +1100
Subject: [PATCH 01/43] Implementation for Amazon DynamoDB
---
README.md | 2 ++
storage/ddb/69-ddbout.html | 72 ++++++++++++++++++++++++++++++++++++++
storage/ddb/69-ddbout.js | 45 ++++++++++++++++++++++++
3 files changed, 119 insertions(+)
create mode 100644 storage/ddb/69-ddbout.html
create mode 100644 storage/ddb/69-ddbout.js
diff --git a/README.md b/README.md
index 5c150f4c..fa93915e 100644
--- a/README.md
+++ b/README.md
@@ -96,6 +96,8 @@ Uses a simple read of the serial port as a file to input data. You **must** set
**68-mysql** - Allows basic access to a MySQL database. This node uses the **query** operation against the configured database. This does allow both INSERTS and DELETES. By it's very nature it allows SQL injection... *so be careful out there...*
+**69-ddbout** - Support output to Amazon DynamoDB.
+
### Time
**79-suncalc** - Uses the suncalc module to generate an output at sunrise and sunset based on a specified location. Several choices of definition of sunrise and sunset are available,
diff --git a/storage/ddb/69-ddbout.html b/storage/ddb/69-ddbout.html
new file mode 100644
index 00000000..70235393
--- /dev/null
+++ b/storage/ddb/69-ddbout.html
@@ -0,0 +1,72 @@
+
+
+
+
+
+
+
diff --git a/storage/ddb/69-ddbout.js b/storage/ddb/69-ddbout.js
new file mode 100644
index 00000000..8f51576b
--- /dev/null
+++ b/storage/ddb/69-ddbout.js
@@ -0,0 +1,45 @@
+/**
+ * Copyright 2013 Wolfgang Nagele
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ **/
+
+var RED = require(process.env.NODE_RED_HOME+"/red/red");
+var util = require("util");
+var aws = require("aws-sdk");
+var attrWrapper = require("dynamodb-data-types").AttributeValue;
+
+function DDBOutNode(n) {
+ RED.nodes.createNode(this, n);
+ this.accessKey = n.accessKey;
+ this.secretAccessKey = n.secretAccessKey;
+ this.region = n.region || "us-east-1";
+ this.table = n.table;
+
+ aws.config.update({ accessKeyId: this.accessKey,
+ secretAccessKey: this.secretAccessKey,
+ region: this.region });
+
+ var ddb = new aws.DynamoDB();
+
+ this.on("input", function(msg) {
+ if (msg != null) {
+ ddb.putItem({ "TableName": this.table,
+ "Item": attrWrapper.wrap(msg.payload) },
+ function(err, data) {
+ err && util.log(err);
+ });
+ }
+ });
+}
+RED.nodes.registerType("ddb out", DDBOutNode);
From 17a040adfe3103200da5b2336584683401a35a57 Mon Sep 17 00:00:00 2001
From: Wolfgang Nagele
Date: Thu, 19 Dec 2013 13:07:40 +1100
Subject: [PATCH 02/43] Support external credentials Add base node with
credentials
---
storage/ddb/69-ddbout.html | 65 +++++++++++++++++++++++++++++++++-----
storage/ddb/69-ddbout.js | 55 +++++++++++++++++++++++++++++---
2 files changed, 108 insertions(+), 12 deletions(-)
diff --git a/storage/ddb/69-ddbout.html b/storage/ddb/69-ddbout.html
index 70235393..33b5463e 100644
--- a/storage/ddb/69-ddbout.html
+++ b/storage/ddb/69-ddbout.html
@@ -16,12 +16,8 @@
+
+
+
+
diff --git a/storage/ddb/69-ddbout.js b/storage/ddb/69-ddbout.js
index 8f51576b..8cf0d753 100644
--- a/storage/ddb/69-ddbout.js
+++ b/storage/ddb/69-ddbout.js
@@ -16,18 +16,65 @@
var RED = require(process.env.NODE_RED_HOME+"/red/red");
var util = require("util");
+var querystring = require('querystring');
var aws = require("aws-sdk");
var attrWrapper = require("dynamodb-data-types").AttributeValue;
+function DDBNode(n) {
+ RED.nodes.createNode(this, n);
+ var credentials = RED.nodes.getCredentials(n.id);
+ if (credentials) {
+ this.accessKey = credentials.accessKey;
+ this.secretAccessKey = credentials.secretAccessKey;
+ }
+}
+RED.nodes.registerType("awscredentials", DDBNode);
+
+RED.app.get('/awscredentials/:id', function(req, res) {
+ var credentials = RED.nodes.getCredentials(req.params.id);
+ if (credentials) {
+ res.send(JSON.stringify({ accessKey: credentials.accessKey, secretAccessKey: credentials.secretAccessKey }));
+ } else {
+ res.send(JSON.stringify({}));
+ }
+});
+
+RED.app.delete('/awscredentials/:id', function(req, res) {
+ RED.nodes.deleteCredentials(req.params.id);
+ res.send(200);
+});
+
+RED.app.post('/awscredentials/:id', function(req, res) {
+ var body = "";
+ req.on("data", function(chunk) {
+ body += chunk;
+ });
+ req.on("end", function() {
+ var newCreds = querystring.parse(body);
+ var credentials = RED.nodes.getCredentials(req.params.id) || {};
+ if (newCreds.accessKey == null || newCreds.accessKey == "") {
+ delete credentials.accessKey;
+ } else {
+ credentials.accessKey = newCreds.accessKey || credentials.accessKey;
+ }
+ if (newCreds.secretAccessKey == null || newCreds.secretAccessKey == "") {
+ delete credentials.secretAccessKey;
+ } else {
+ credentials.secretAccessKey = newCreds.secretAccessKey || credentials.secretAccessKey;
+ }
+ RED.nodes.addCredentials(req.params.id, credentials);
+ res.send(200);
+ });
+});
+
function DDBOutNode(n) {
RED.nodes.createNode(this, n);
- this.accessKey = n.accessKey;
- this.secretAccessKey = n.secretAccessKey;
+ this.credentials = RED.nodes.getNode(n.credentials);
this.region = n.region || "us-east-1";
this.table = n.table;
- aws.config.update({ accessKeyId: this.accessKey,
- secretAccessKey: this.secretAccessKey,
+ aws.config.update({ accessKeyId: this.credentials.accessKey,
+ secretAccessKey: this.credentials.secretAccessKey,
region: this.region });
var ddb = new aws.DynamoDB();
From fe109f15d84f3627b3c9867afdedc6de9351941a Mon Sep 17 00:00:00 2001
From: Kris Daniels
Date: Fri, 20 Dec 2013 10:00:35 +0100
Subject: [PATCH 03/43] added postgres node
---
storage/postgres/110-postgresout.html | 148 ++++++++++++++++++++++++++
storage/postgres/110-postgresout.js | 83 +++++++++++++++
storage/postgres/icons/postgres.png | Bin 0 -> 625 bytes
3 files changed, 231 insertions(+)
create mode 100644 storage/postgres/110-postgresout.html
create mode 100644 storage/postgres/110-postgresout.js
create mode 100644 storage/postgres/icons/postgres.png
diff --git a/storage/postgres/110-postgresout.html b/storage/postgres/110-postgresout.html
new file mode 100644
index 00000000..ab3291cc
--- /dev/null
+++ b/storage/postgres/110-postgresout.html
@@ -0,0 +1,148 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/storage/postgres/110-postgresout.js b/storage/postgres/110-postgresout.js
new file mode 100644
index 00000000..bf882b6a
--- /dev/null
+++ b/storage/postgres/110-postgresout.js
@@ -0,0 +1,83 @@
+/**
+ * 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.
+ **/
+
+// If you use this as a template, replace IBM Corp. with your own name.
+
+// Sample Node-RED node file
+
+// Require main module
+var RED = require(process.env.NODE_RED_HOME+"/red/red");
+var pg=require('pg');
+var named=require('node-postgres-named');
+
+function PostgresDatabaseNode(n) {
+ RED.nodes.createNode(this,n);
+ this.hostname = n.hostname;
+ this.port = n.port;
+ this.db = n.db;
+ this.username = n.username;
+ this.password = n.password;
+}
+
+RED.nodes.registerType("postgresdb",PostgresDatabaseNode);
+
+function PostgresNode(n) {
+ RED.nodes.createNode(this,n);
+
+ this.topic = n.topic;
+ this.postgresdb = n.postgresdb;
+ this.postgresConfig = RED.nodes.getNode(this.postgresdb);
+ this.sqlquery = n.sqlquery;
+ this.outputs = n.outputs;
+
+ var node = this;
+ if(this.postgresConfig)
+ {
+
+ var conString = 'postgres://'+this.postgresConfig.username +':' + this.postgresConfig.password + '@' + this.postgresConfig.hostname + ':' + this.postgresConfig.port + '/' + this.postgresConfig.db;
+ node.clientdb = new pg.Client(conString);
+ named.patch(node.clientdb);
+
+ node.clientdb.connect(function(err){
+ if(err) { node.error(err); }
+ else {
+ node.on('input',
+ function(msg){
+ node.clientdb.query(node.sqlquery,
+ msg.payload,
+ function (err, results) {
+ if(err) { node.error(err); }
+ else {
+ if(node.outputs>0)
+ {
+ msg.payload = results.rows;
+ node.send(msg);
+ }
+ }
+ });
+ });
+ }
+ });
+ } else {
+ this.error("missing postgres configuration");
+ }
+
+ this.on("close", function() {
+ if(node.clientdb) node.clientdb.end();
+ });
+}
+
+RED.nodes.registerType("postgres",PostgresNode);
diff --git a/storage/postgres/icons/postgres.png b/storage/postgres/icons/postgres.png
new file mode 100644
index 0000000000000000000000000000000000000000..e46c31696bc4e2240ffe3450323e74c6ae0bbe33
GIT binary patch
literal 625
zcmV-%0*?KOP)HWn7*8>A2=
zPoR*(#?ID4Ng-Z2`D|v#*^}9G_Q^$X!NAO#%Ubi#|Ib1tYvF+7Tk2bNTU{%hi%RaO
z+0u;pmE@e8iN6ygs6-Pu4SWN3*>3`iz|YLz01Lo(U>VqnYtwxb;8(jiL}viLR)SqD
z1S78h0gQn!z`a63fA^_p)z^KPtEWoPRNA)M6#TZ38nckHwYMK?4KOY25eQ8(I4au(
z9s{?_>Cc9%ou8YeFVsS8^B56MPbce|0o0Y21)zP49ZV{twXZ5lU%F6
zJHXY#;uYZ09EXQYVxc4p3Xj~jvHH0{@5uleslHJ!m#W36qDQlmG~}C8-7M_g)8Q0&
z3as_KWBZ|>2c8dv_gehW?Sr&0d(}_s{jC1=-ELH=nfn4h$;NjxSWG^E8l+1-g}qaN
z`?N%Xbv4~C#$ue>0`QxNagEwd@_w%dR+aT6nOr8t8`f+T2{!M~)G3zp*z~Z)vf&@w
ze-m)ypaJJ4O^qDmNd0h}kP55
Date: Fri, 20 Dec 2013 10:18:22 +0100
Subject: [PATCH 04/43] added examples to node documentation
---
storage/postgres/110-postgresout.html | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/storage/postgres/110-postgresout.html b/storage/postgres/110-postgresout.html
index ab3291cc..4e984f5a 100644
--- a/storage/postgres/110-postgresout.html
+++ b/storage/postgres/110-postgresout.html
@@ -78,6 +78,16 @@
This node can be used to write and read from a Postgres database
To receive the results from a query, simply set the outputs to 1 and connect a node
The msg.payload on the output will be a json array of the returned records
+
+
Insert exampleb>
+
msg.payload = { 'sensorid': 1, 'value': 2 }
+
insert into table (field1, field2) values ($sensorid, $value)
+
+
+
Select exampleb>
+
msg.payload = { 'sensorid': 1, 'value': 2 }
+
select * from table where field1=$sensorid order by time desc limit 1
+
diff --git a/storage/postgres/110-postgres.js b/storage/postgres/110-postgres.js
index bf882b6a..36e02b05 100644
--- a/storage/postgres/110-postgres.js
+++ b/storage/postgres/110-postgres.js
@@ -1,5 +1,5 @@
/**
- * Copyright 2013 IBM Corp.
+ * Copyright 2013 Kris Daniels.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,11 +14,6 @@
* limitations under the License.
**/
-// If you use this as a template, replace IBM Corp. with your own name.
-
-// Sample Node-RED node file
-
-// Require main module
var RED = require(process.env.NODE_RED_HOME+"/red/red");
var pg=require('pg');
var named=require('node-postgres-named');
From 43c6bdb95ad71b9f7e691de467e8634e053c4f97 Mon Sep 17 00:00:00 2001
From: Kris Daniels
Date: Sat, 21 Dec 2013 09:23:46 +0100
Subject: [PATCH 07/43] removed script editor from node config added support
for script in msg.payload added support for query parameters in
msg.queryParameters
---
storage/postgres/110-postgres.html | 83 +++++-------------------------
storage/postgres/110-postgres.js | 9 ++--
2 files changed, 17 insertions(+), 75 deletions(-)
diff --git a/storage/postgres/110-postgres.html b/storage/postgres/110-postgres.html
index 09a887a8..033cc990 100644
--- a/storage/postgres/110-postgres.html
+++ b/storage/postgres/110-postgres.html
@@ -60,43 +60,18 @@
-
-
-
-
-
-
-
+
+
+
@@ -107,8 +82,7 @@ limit 1
defaults: {
postgresdb: { type:"postgresdb",required:true},
name: {value:""},
- sqlquery: {value:"",required:true},
- outputs: {value:0}
+ output: {value:false}
},
inputs:1,
outputs:0,
@@ -121,47 +95,14 @@ limit 1
return this.name?"node_label_italic":"";
},
oneditprepare: function() {
- $( "#node-input-outputs" ).spinner({
- min:0,
- max:1
- });
- $( "#node-input-outputs" ).spinner( "value", this.outputs );
+ $( "#node-input-output" ).prop( "checked", this.output );
+ $("#node-input-name").focus();
- function functionDialogResize(ev,ui) {
- $("#node-input-sqlquery-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-sqlquery-editor'),
- lang:"sql",
- contents: $("#node-input-sqlquery").val()
- });
- RED.library.create({
- url:"storage", // where to get the data from
- type:"postgres", // 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-sqlquery").val(this.editor.getText())
+ var hasOutput = $( "#node-input-output" ).prop( "checked" );
+ this.outputs = hasOutput ? 1: 0;
delete this.editor;
}
});
diff --git a/storage/postgres/110-postgres.js b/storage/postgres/110-postgres.js
index 36e02b05..edd42c88 100644
--- a/storage/postgres/110-postgres.js
+++ b/storage/postgres/110-postgres.js
@@ -36,7 +36,7 @@ function PostgresNode(n) {
this.postgresdb = n.postgresdb;
this.postgresConfig = RED.nodes.getNode(this.postgresdb);
this.sqlquery = n.sqlquery;
- this.outputs = n.outputs;
+ this.output = n.output;
var node = this;
if(this.postgresConfig)
@@ -51,12 +51,13 @@ function PostgresNode(n) {
else {
node.on('input',
function(msg){
- node.clientdb.query(node.sqlquery,
- msg.payload,
+ if(!msg.queryParameters) msg.queryParameters={};
+ node.clientdb.query(msg.payload,
+ msg.queryParameters,
function (err, results) {
if(err) { node.error(err); }
else {
- if(node.outputs>0)
+ if(node.output)
{
msg.payload = results.rows;
node.send(msg);
From 497f08bba93a001872ffa01907fac99d3adbccfb Mon Sep 17 00:00:00 2001
From: Kris Daniels
Date: Sat, 21 Dec 2013 11:01:19 +0100
Subject: [PATCH 08/43] moved credtials from config to credential store
---
storage/postgres/110-postgres.html | 47 ++++++++++++++++++++++++----
storage/postgres/110-postgres.js | 50 ++++++++++++++++++++++++++++--
2 files changed, 88 insertions(+), 9 deletions(-)
diff --git a/storage/postgres/110-postgres.html b/storage/postgres/110-postgres.html
index 033cc990..a4ea5e57 100644
--- a/storage/postgres/110-postgres.html
+++ b/storage/postgres/110-postgres.html
@@ -27,7 +27,7 @@
-
+
@@ -40,12 +40,46 @@
defaults: {
hostname: { value:"localhost",required:true},
port: { value: 5432,required:true},
- db: { value:"postgres",required:true},
- username: { value:"postgres", required:true },
- password: { value:"postgres", required:true }
+ db: { value:"postgres",required:true}
},
label: function() {
return this.name||this.hostname+":"+this.port+"/"+this.db;
+ },
+ oneditprepare: function() {
+
+ $.getJSON('postgresdb/'+this.id,function(data) {
+ if (data.user) {
+ $('#node-config-input-user').val(data.user);
+ }
+ if (data.hasPassword) {
+ $('#node-config-input-password').val('__PWRD__');
+ } else {
+ $('#node-config-input-password').val('');
+ }
+ });
+ },
+ oneditsave: function() {
+
+ var newUser = $('#node-config-input-user').val();
+ var newPass = $('#node-config-input-password').val();
+ var credentials = {};
+ credentials.user = newUser;
+ if (newPass != '__PWRD__') {
+ credentials.password = newPass;
+ }
+ $.ajax({
+ url: 'postgresdb/'+this.id,
+ type: 'POST',
+ data: credentials,
+ success:function(result){}
+ });
+ },
+ ondelete: function() {
+ $.ajax({
+ url: 'postgresdb/'+this.id,
+ type: 'DELETE',
+ success: function(result) {}
+ });
}
});
@@ -98,12 +132,13 @@
$( "#node-input-output" ).prop( "checked", this.output );
$("#node-input-name").focus();
-
+
},
oneditsave: function() {
+
var hasOutput = $( "#node-input-output" ).prop( "checked" );
this.outputs = hasOutput ? 1: 0;
- delete this.editor;
+
}
});
diff --git a/storage/postgres/110-postgres.js b/storage/postgres/110-postgres.js
index edd42c88..4e2d41ee 100644
--- a/storage/postgres/110-postgres.js
+++ b/storage/postgres/110-postgres.js
@@ -17,14 +17,57 @@
var RED = require(process.env.NODE_RED_HOME+"/red/red");
var pg=require('pg');
var named=require('node-postgres-named');
+var querystring = require('querystring');
+
+RED.app.get('/postgresdb/:id',function(req,res) {
+ var credentials = RED.nodes.getCredentials(req.params.id);
+ if (credentials) {
+ res.send(JSON.stringify({user:credentials.user,hasPassword:(credentials.password&&credentials.password!="")}));
+ } else {
+ res.send(JSON.stringify({}));
+ }
+});
+
+RED.app.delete('/postgresdb/:id',function(req,res) {
+ RED.nodes.deleteCredentials(req.params.id);
+ res.send(200);
+});
+
+RED.app.post('/postgresdb/:id',function(req,res) {
+ var body = "";
+ req.on('data', function(chunk) {
+ body+=chunk;
+ });
+ req.on('end', function(){
+ var newCreds = querystring.parse(body);
+ var credentials = RED.nodes.getCredentials(req.params.id)||{};
+ if (newCreds.user == null || newCreds.user == "") {
+ delete credentials.user;
+ } else {
+ credentials.user = newCreds.user;
+ }
+ if (newCreds.password == "") {
+ delete credentials.password;
+ } else {
+ credentials.password = newCreds.password||credentials.password;
+ }
+ RED.nodes.addCredentials(req.params.id,credentials);
+ res.send(200);
+ });
+});
+
function PostgresDatabaseNode(n) {
RED.nodes.createNode(this,n);
this.hostname = n.hostname;
this.port = n.port;
this.db = n.db;
- this.username = n.username;
- this.password = n.password;
+
+ var credentials = RED.nodes.getCredentials(n.id);
+ if (credentials) {
+ this.user = credentials.user;
+ this.password = credentials.password;
+ }
}
RED.nodes.registerType("postgresdb",PostgresDatabaseNode);
@@ -39,10 +82,11 @@ function PostgresNode(n) {
this.output = n.output;
var node = this;
+
if(this.postgresConfig)
{
- var conString = 'postgres://'+this.postgresConfig.username +':' + this.postgresConfig.password + '@' + this.postgresConfig.hostname + ':' + this.postgresConfig.port + '/' + this.postgresConfig.db;
+ var conString = 'postgres://'+this.postgresConfig.user +':' + this.postgresConfig.password + '@' + this.postgresConfig.hostname + ':' + this.postgresConfig.port + '/' + this.postgresConfig.db;
node.clientdb = new pg.Client(conString);
named.patch(node.clientdb);
From e72ced85e6f5ec18b8e6c3489a9d565ccba7a934 Mon Sep 17 00:00:00 2001
From: Kris Daniels
Date: Sat, 21 Dec 2013 16:55:44 +0100
Subject: [PATCH 09/43] fixed bug with outputs not being set correctly after
initial load
---
storage/postgres/110-postgres.html | 60 +++++++++++++++---------------
1 file changed, 31 insertions(+), 29 deletions(-)
diff --git a/storage/postgres/110-postgres.html b/storage/postgres/110-postgres.html
index a4ea5e57..6fae6ac1 100644
--- a/storage/postgres/110-postgres.html
+++ b/storage/postgres/110-postgres.html
@@ -111,34 +111,36 @@
From 243fc9ffd4da4de5e73d2252aba892abb18581e5 Mon Sep 17 00:00:00 2001
From: Wolfgang Nagele
Date: Sat, 21 Dec 2013 18:44:50 +0100
Subject: [PATCH 10/43] Setting defaults with credentials fields persists them
in the flows file. Fixed.
---
storage/ddb/69-ddbout.html | 4 ----
1 file changed, 4 deletions(-)
diff --git a/storage/ddb/69-ddbout.html b/storage/ddb/69-ddbout.html
index 33b5463e..dfe8dac0 100644
--- a/storage/ddb/69-ddbout.html
+++ b/storage/ddb/69-ddbout.html
@@ -80,10 +80,6 @@
+
+
diff --git a/lib/aws.js b/lib/aws.js
new file mode 100644
index 00000000..9997b676
--- /dev/null
+++ b/lib/aws.js
@@ -0,0 +1,65 @@
+/**
+ * Copyright 2013 Wolfgang Nagele
+ *
+ * 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 querystring = require('querystring');
+
+function AWSCredentialsNode(n) {
+ RED.nodes.createNode(this, n);
+ var credentials = RED.nodes.getCredentials(n.id);
+ if (credentials) {
+ this.accessKey = credentials.accessKey;
+ this.secretAccessKey = credentials.secretAccessKey;
+ }
+}
+RED.nodes.registerType("aws credentials", AWSCredentialsNode);
+
+RED.app.get('/aws-credentials/:id', function(req, res) {
+ var credentials = RED.nodes.getCredentials(req.params.id);
+ if (credentials) {
+ res.send(JSON.stringify({ accessKey: credentials.accessKey, secretAccessKey: credentials.secretAccessKey }));
+ } else {
+ res.send(JSON.stringify({}));
+ }
+});
+
+RED.app.delete('/aws-credentials/:id', function(req, res) {
+ RED.nodes.deleteCredentials(req.params.id);
+ res.send(200);
+});
+
+RED.app.post('/aws-credentials/:id', function(req, res) {
+ var body = "";
+ req.on("data", function(chunk) {
+ body += chunk;
+ });
+ req.on("end", function() {
+ var newCreds = querystring.parse(body);
+ var credentials = RED.nodes.getCredentials(req.params.id) || {};
+ if (newCreds.accessKey == null || newCreds.accessKey == "") {
+ delete credentials.accessKey;
+ } else {
+ credentials.accessKey = newCreds.accessKey || credentials.accessKey;
+ }
+ if (newCreds.secretAccessKey == null || newCreds.secretAccessKey == "") {
+ delete credentials.secretAccessKey;
+ } else {
+ credentials.secretAccessKey = newCreds.secretAccessKey || credentials.secretAccessKey;
+ }
+ RED.nodes.addCredentials(req.params.id, credentials);
+ res.send(200);
+ });
+});
diff --git a/storage/ddb/69-ddbout.html b/storage/ddb/69-ddbout.html
index dfe8dac0..4262ea5a 100644
--- a/storage/ddb/69-ddbout.html
+++ b/storage/ddb/69-ddbout.html
@@ -51,7 +51,7 @@
category: "storage-output",
color: "#ffaaaa",
defaults: {
- credentials: { type: "awscredentials", required: true },
+ credentials: { type: "aws credentials", required: true },
region: { value: "us-east-1" },
table: { value: "", required: true },
name: { value: "" }
@@ -65,53 +65,3 @@
}
});
-
-
-
-
diff --git a/storage/ddb/69-ddbout.js b/storage/ddb/69-ddbout.js
index 8cf0d753..3a9120d5 100644
--- a/storage/ddb/69-ddbout.js
+++ b/storage/ddb/69-ddbout.js
@@ -15,58 +15,11 @@
**/
var RED = require(process.env.NODE_RED_HOME+"/red/red");
+require("../../lib/aws");
var util = require("util");
-var querystring = require('querystring');
var aws = require("aws-sdk");
var attrWrapper = require("dynamodb-data-types").AttributeValue;
-function DDBNode(n) {
- RED.nodes.createNode(this, n);
- var credentials = RED.nodes.getCredentials(n.id);
- if (credentials) {
- this.accessKey = credentials.accessKey;
- this.secretAccessKey = credentials.secretAccessKey;
- }
-}
-RED.nodes.registerType("awscredentials", DDBNode);
-
-RED.app.get('/awscredentials/:id', function(req, res) {
- var credentials = RED.nodes.getCredentials(req.params.id);
- if (credentials) {
- res.send(JSON.stringify({ accessKey: credentials.accessKey, secretAccessKey: credentials.secretAccessKey }));
- } else {
- res.send(JSON.stringify({}));
- }
-});
-
-RED.app.delete('/awscredentials/:id', function(req, res) {
- RED.nodes.deleteCredentials(req.params.id);
- res.send(200);
-});
-
-RED.app.post('/awscredentials/:id', function(req, res) {
- var body = "";
- req.on("data", function(chunk) {
- body += chunk;
- });
- req.on("end", function() {
- var newCreds = querystring.parse(body);
- var credentials = RED.nodes.getCredentials(req.params.id) || {};
- if (newCreds.accessKey == null || newCreds.accessKey == "") {
- delete credentials.accessKey;
- } else {
- credentials.accessKey = newCreds.accessKey || credentials.accessKey;
- }
- if (newCreds.secretAccessKey == null || newCreds.secretAccessKey == "") {
- delete credentials.secretAccessKey;
- } else {
- credentials.secretAccessKey = newCreds.secretAccessKey || credentials.secretAccessKey;
- }
- RED.nodes.addCredentials(req.params.id, credentials);
- res.send(200);
- });
-});
-
function DDBOutNode(n) {
RED.nodes.createNode(this, n);
this.credentials = RED.nodes.getNode(n.credentials);
From 4e20f70bfde35b31427f36ca1231322252f69985 Mon Sep 17 00:00:00 2001
From: Wolfgang Nagele
Date: Sun, 22 Dec 2013 08:48:02 +0100
Subject: [PATCH 12/43] Leave region selection open so new regions are quicker
to use.
---
storage/ddb/69-ddbout.html | 13 ++-----------
1 file changed, 2 insertions(+), 11 deletions(-)
diff --git a/storage/ddb/69-ddbout.html b/storage/ddb/69-ddbout.html
index 4262ea5a..3f0859bd 100644
--- a/storage/ddb/69-ddbout.html
+++ b/storage/ddb/69-ddbout.html
@@ -21,16 +21,7 @@
-
+
@@ -52,7 +43,7 @@
color: "#ffaaaa",
defaults: {
credentials: { type: "aws credentials", required: true },
- region: { value: "us-east-1" },
+ region: { value: "us-east-1", required: true },
table: { value: "", required: true },
name: { value: "" }
},
From 5342637132108a6914f99c2960088d72edadc0ee Mon Sep 17 00:00:00 2001
From: Wolfgang Nagele
Date: Tue, 24 Dec 2013 17:21:22 +0100
Subject: [PATCH 13/43] Need to retain the accessKey defaults to be able to
show them in the UI
---
lib/aws.html | 3 +++
1 file changed, 3 insertions(+)
diff --git a/lib/aws.html b/lib/aws.html
index da463c52..5c26c904 100644
--- a/lib/aws.html
+++ b/lib/aws.html
@@ -31,6 +31,9 @@
label: function() {
return this.accessKey;
},
+ defaults: {
+ accessKey: { value: "", required: true }
+ },
oneditprepare: function() {
$.getJSON("aws-credentials/" + this.id, function(data) {
if (data.accessKey) {
From efb9fbb92b10dee2d8c93b0f9a29745db8970f46 Mon Sep 17 00:00:00 2001
From: Wolfgang Nagele
Date: Sun, 2 Feb 2014 21:52:08 +1100
Subject: [PATCH 14/43] Move AWS credentials to DDB node
---
storage/ddb/69-ddbout.js | 1 -
{lib => storage/ddb}/aws.html | 0
{lib => storage/ddb}/aws.js | 0
3 files changed, 1 deletion(-)
rename {lib => storage/ddb}/aws.html (100%)
rename {lib => storage/ddb}/aws.js (100%)
diff --git a/storage/ddb/69-ddbout.js b/storage/ddb/69-ddbout.js
index 3a9120d5..939d7d23 100644
--- a/storage/ddb/69-ddbout.js
+++ b/storage/ddb/69-ddbout.js
@@ -15,7 +15,6 @@
**/
var RED = require(process.env.NODE_RED_HOME+"/red/red");
-require("../../lib/aws");
var util = require("util");
var aws = require("aws-sdk");
var attrWrapper = require("dynamodb-data-types").AttributeValue;
diff --git a/lib/aws.html b/storage/ddb/aws.html
similarity index 100%
rename from lib/aws.html
rename to storage/ddb/aws.html
diff --git a/lib/aws.js b/storage/ddb/aws.js
similarity index 100%
rename from lib/aws.js
rename to storage/ddb/aws.js
From 62d26c04fbdfa1d1ae915d425330b55b5aeb848f Mon Sep 17 00:00:00 2001
From: Maxwell Hadley
Date: Mon, 3 Feb 2014 20:06:41 +0000
Subject: [PATCH 15/43] Added BBB-analog-in
Added a triggered analogue input node
---
hardware/BBB/144-analog-in.html | 85 +++++++++++++++++++++++++++++++++
hardware/BBB/144-analog-in.js | 62 ++++++++++++++++++++++++
2 files changed, 147 insertions(+)
create mode 100644 hardware/BBB/144-analog-in.html
create mode 100644 hardware/BBB/144-analog-in.js
diff --git a/hardware/BBB/144-analog-in.html b/hardware/BBB/144-analog-in.html
new file mode 100644
index 00000000..319da5b8
--- /dev/null
+++ b/hardware/BBB/144-analog-in.html
@@ -0,0 +1,85 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/hardware/BBB/144-analog-in.js b/hardware/BBB/144-analog-in.js
new file mode 100644
index 00000000..bd5a6679
--- /dev/null
+++ b/hardware/BBB/144-analog-in.js
@@ -0,0 +1,62 @@
+/**
+ * Copyright 2014 Maxwell R Hadley
+ *
+ * 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.
+ **/
+
+// Require main module
+var RED = require(process.env.NODE_RED_HOME+"/red/red");
+
+// Require bonescript
+try {
+ var bs = require("bonescript");
+} catch(err) {
+ require("util").log("[BBB-analog-in] Error: cannot find module 'bonescript'");
+}
+
+// The main node definition - most things happen in here
+function AnalogInputNode(n) {
+ // Create a RED node
+ RED.nodes.createNode(this, n);
+
+ // Store local copies of the node configuration (as defined in the .html)
+ this.topic = n.topic;
+ this.pin = n.pin;
+
+ // Define 'node' to allow us to access 'this' from within callbacks (the 'var' is essential -
+ // otherwise there is only one 'node' for all instances of AnalogInputNode!)
+ var node = this;
+
+ // A callback function variable seems to be more reliable than a lambda ?!
+ var cbFun = function (x) {
+ var msg = {};
+ msg.topic = node.topic;
+ msg.payload = x.value;
+ if (isNaN(x.value)) {
+ this.log(x.err);
+ }
+ node.send(msg);
+ };
+
+ // If we have a valid pin, set the input event handler to Bonescript's analogRead
+ if (["P9_39", "P9_40", "P9_37", "P9_38", "P9_33", "P9_36", "P9_35"].indexOf(node.pin) >= 0) {
+ node.on("input", function (msg) { bs.analogRead(node.pin, cbFun) });
+ } else {
+ node.error("Unconfigured input pin");
+ }
+}
+
+// Register the node by name. This must be called before overriding any of the Node functions.
+RED.nodes.registerType("BBB-analog-in", AnalogInputNode);
+
+// AnalogInputNode.prototype.close = function () { };
From 83f7ff429f68e33bbfec768503f69651257cd315 Mon Sep 17 00:00:00 2001
From: Maxwell Hadley
Date: Mon, 3 Feb 2014 20:08:33 +0000
Subject: [PATCH 16/43] Added BBB-digital-in
Added digital input node - initially this is just BBB-discrete-in
---
hardware/BBB/145-digital-in.html | 111 +++++++++++++++++++++++++
hardware/BBB/145-digital-in.js | 137 +++++++++++++++++++++++++++++++
2 files changed, 248 insertions(+)
create mode 100644 hardware/BBB/145-digital-in.html
create mode 100644 hardware/BBB/145-digital-in.js
diff --git a/hardware/BBB/145-digital-in.html b/hardware/BBB/145-digital-in.html
new file mode 100644
index 00000000..dcca5583
--- /dev/null
+++ b/hardware/BBB/145-digital-in.html
@@ -0,0 +1,111 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/hardware/BBB/145-digital-in.js b/hardware/BBB/145-digital-in.js
new file mode 100644
index 00000000..0b98d1d4
--- /dev/null
+++ b/hardware/BBB/145-digital-in.js
@@ -0,0 +1,137 @@
+/**
+ * Copyright 2014 Maxwell R Hadley
+ *
+ * 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.
+ **/
+
+// Require main module
+var RED = require(process.env.NODE_RED_HOME + "/red/red");
+
+// Require bonescript
+try {
+ var bs = require("bonescript");
+} catch(err) {
+ require("util").log("[BBB-discrete-in] Error: cannot find module 'bonescript'");
+}
+
+// The node constructor
+function DiscreteInputNode(n) {
+ RED.nodes.createNode(this, n);
+
+ // Store local copies of the node configuration (as defined in the .html)
+ this.topic = n.topic; // the topic is not currently used
+ this.pin = n.pin; // The Beaglebone Black pin identifying string
+ this.updateInterval = n.updateInterval*1000; // How often to send total active time messages
+
+ // Define 'node' to allow us to access 'this' from within callbacks (the 'var' is essential -
+ // otherwise there is only one 'node' for all instances of DiscreteInputNode!)
+ var node = this;
+ this.interruptAttached = false; // Flag: should we detach interrupt when we are closed?
+ this.intervalId = null; // Remember the timer ID so we can delete it when we are closed
+ this.currentState = 0; // The pin input state "1" or "0"
+ this.lastActiveTime = 0; // The date (in ms since epoch) when the pin last went high
+ this.totalActiveTime = 0; // The total time in ms that the pin has been high (since reset)
+ this.starting = true;
+
+ // This function is called whenver the input pin changes state. We update the currentState
+ // and the ActiveTime variables, and send a message on the first output with the new state
+ var interruptCallback = function (x) {
+ if (node.currentState == x.value) {
+ node.error("Spurious interrupt" + x.value);
+ } else {
+ node.currentState = x.value;
+ var now = Date.now();
+ if (node.currentState == "1") {
+ node.lastActiveTime = now;
+ } else {
+ node.totalActiveTime += now - node.lastActiveTime;
+ }
+ }
+ var msg = {};
+ msg.payload = node.currentState;
+ node.send([msg, null]);
+ };
+
+ // This function is called by the timer. It updates the ActiveTime variables, and sends a
+ // message on the second output with the latest value of the total active time, in seconds
+ var timerCallback = function () {
+ if (node.currentState == "1") {
+ var now = Date.now();
+ node.totalActiveTime += now - node.lastActiveTime;
+ node.lastActiveTime = now;
+ }
+ var msg = {};
+ msg.payload = node.totalActiveTime/1000;
+ node.send([null, msg]);
+ };
+
+ // This function is called when we receive an input message. Clear the ActiveTime variables
+ // (so we start counting from zero again)
+ var inputCallback = function (msg) {
+ node.totalActiveTime = 0;
+ if (node.currentState == "1") {
+ node.lastActiveTime = Date.now();
+ }
+ if (node.starting) {
+ node.starting = 0;
+ var msg1 = {};
+ msg1.payload = node.currentState;
+ var msg2 = {};
+ msg2.payload = node.totalActiveTime/1000;
+ node.send([null, msg2]);
+ node.send([msg1, null]);
+ node.error("Initial message" + msg1 + " " + msg2);
+ }
+ };
+
+ // If we have a valid pin, set it as an input and read the (digital) state
+ if (["P8_7", "P8_8", "P8_9", "P8_10", "P8_11", "P8_12", "P8_13", "P8_14", "P8_15",
+ "P8_16", "P8_17", "P8_18", "P8_19", "P8_26", "P9_11", "P9_12", "P9_13", "P9_14",
+ "P9_15", "P9_16", "P9_17", "P9_18", "P9_21", "P9_22", "P9_23", "P9_24", "P9_26",
+ "P9_27", "P9_30", "P9_41", "P9_42"].indexOf(node.pin) >= 0) {
+ bs.pinMode(node.pin, bs.INPUT);
+ bs.digitalRead(node.pin, function (x) {
+ // Initialise the currentState and lastActveTime variables based on the value read
+ node.currentState = x;
+ if (node.currentState == "1") {
+ node.lastActiveTime = Date.now();
+ }
+ // Attempt to attach a change-of-state interrupt handler to the pin. If we succeed,
+ // set the input event and interval handlers, then send an initial message with the
+ // pin state on the first output
+ if (bs.attachInterrupt(node.pin, true, bs.CHANGE, interruptCallback)) {
+ node.interruptAttached = true;
+ node.on("input", inputCallback);
+ node.intervalId = setInterval(timerCallback, node.updateInterval);
+ } else {
+ node.error("Failed to attach interrupt");
+ }
+ setTimeout(function () { node.starting = 1; node.emit("input", {}); }, 250);
+ });
+ } else {
+ node.error("Unconfigured input pin");
+ }
+}
+
+// Register the node by name. This must be called before overriding any of the Node functions.
+RED.nodes.registerType("BBB-discrete-in", DiscreteInputNode);
+
+// On close, detach the interrupt (if we attaced one) and clear the interval (if we set one)
+DiscreteInputNode.prototype.close = function () {
+ if (this.interruptAttached) {
+ bs.detachInterrupt(this.pin);
+ }
+ if (this.intervalId!= null) {
+ clearInterval(this.intervalId);
+ }
+};
From 3b319e7d83f5c261bbda204487fb228f635b57c1 Mon Sep 17 00:00:00 2001
From: Maxwell Hadley
Date: Mon, 3 Feb 2014 21:25:25 +0000
Subject: [PATCH 17/43] Attempting to get the initial message
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Modified 145-digital-in.js adding various debugs to try and trace why
the initial messages don’t appear
---
hardware/BBB/145-digital-in.js | 43 +++++++++++++++++++---------------
1 file changed, 24 insertions(+), 19 deletions(-)
diff --git a/hardware/BBB/145-digital-in.js b/hardware/BBB/145-digital-in.js
index 0b98d1d4..08e437a1 100644
--- a/hardware/BBB/145-digital-in.js
+++ b/hardware/BBB/145-digital-in.js
@@ -29,25 +29,26 @@ function DiscreteInputNode(n) {
RED.nodes.createNode(this, n);
// Store local copies of the node configuration (as defined in the .html)
- this.topic = n.topic; // the topic is not currently used
- this.pin = n.pin; // The Beaglebone Black pin identifying string
- this.updateInterval = n.updateInterval*1000; // How often to send total active time messages
+ this.topic = n.topic; // the topic is not currently used
+ this.pin = n.pin; // The Beaglebone Black pin identifying string
+ this.updateInterval = n.updateInterval*1000; // How often to send total active time messages
- // Define 'node' to allow us to access 'this' from within callbacks (the 'var' is essential -
- // otherwise there is only one 'node' for all instances of DiscreteInputNode!)
- var node = this;
this.interruptAttached = false; // Flag: should we detach interrupt when we are closed?
this.intervalId = null; // Remember the timer ID so we can delete it when we are closed
this.currentState = 0; // The pin input state "1" or "0"
this.lastActiveTime = 0; // The date (in ms since epoch) when the pin last went high
this.totalActiveTime = 0; // The total time in ms that the pin has been high (since reset)
this.starting = true;
+
+ // Define 'node' to allow us to access 'this' from within callbacks (the 'var' is essential -
+ // otherwise there is only one 'node' for all instances of DiscreteInputNode!)
+ var node = this;
- // This function is called whenver the input pin changes state. We update the currentState
+ // This function is called whenever the input pin changes state. We update the currentState
// and the ActiveTime variables, and send a message on the first output with the new state
var interruptCallback = function (x) {
if (node.currentState == x.value) {
- node.error("Spurious interrupt" + x.value);
+ node.log("Spurious interrupt: " + x.value);
} else {
node.currentState = x.value;
var now = Date.now();
@@ -82,16 +83,19 @@ function DiscreteInputNode(n) {
if (node.currentState == "1") {
node.lastActiveTime = Date.now();
}
- if (node.starting) {
- node.starting = 0;
+ if (node.starting) {
+ node.starting = false;
var msg1 = {};
- msg1.payload = node.currentState;
+ msg1.payload = "hello";
var msg2 = {};
- msg2.payload = node.totalActiveTime/1000;
- node.send([null, msg2]);
- node.send([msg1, null]);
- node.error("Initial message" + msg1 + " " + msg2);
- }
+ msg2.payload = "world";
+ this.send([msg1, msg2]);
+ node.log("Initial message " + msg1.payload + " " + msg2.payload);
+ node.log("currentState: " + node.currentState);
+ node.log("activeTime: " + node.totalActiveTime);
+ msg1 = null;
+ msg2 = null;
+ }
};
// If we have a valid pin, set it as an input and read the (digital) state
@@ -102,7 +106,8 @@ function DiscreteInputNode(n) {
bs.pinMode(node.pin, bs.INPUT);
bs.digitalRead(node.pin, function (x) {
// Initialise the currentState and lastActveTime variables based on the value read
- node.currentState = x;
+ node.currentState = x.value;
+ node.error("First read - currentState: " + node.currentState);
if (node.currentState == "1") {
node.lastActiveTime = Date.now();
}
@@ -116,7 +121,7 @@ function DiscreteInputNode(n) {
} else {
node.error("Failed to attach interrupt");
}
- setTimeout(function () { node.starting = 1; node.emit("input", {}); }, 250);
+ setTimeout(function () { node.emit("input", {}); }, 50);
});
} else {
node.error("Unconfigured input pin");
@@ -131,7 +136,7 @@ DiscreteInputNode.prototype.close = function () {
if (this.interruptAttached) {
bs.detachInterrupt(this.pin);
}
- if (this.intervalId!= null) {
+ if (this.intervalId != null) {
clearInterval(this.intervalId);
}
};
From e9a860f36fee88333d056cfa44af32c046413335 Mon Sep 17 00:00:00 2001
From: Maxwell Hadley
Date: Tue, 4 Feb 2014 20:34:04 +0000
Subject: [PATCH 18/43] Added activeLow property to discrete-in
Further attempts to get the initial message working. Added activeLow
property. Output messages now include topic. Better help text. Change
name to discrete-in
---
hardware/BBB/145-digital-in.html | 40 ++++++++++++++++---------------
hardware/BBB/145-digital-in.js | 41 +++++++++++++++++---------------
2 files changed, 43 insertions(+), 38 deletions(-)
diff --git a/hardware/BBB/145-digital-in.html b/hardware/BBB/145-digital-in.html
index dcca5583..a4d2d251 100644
--- a/hardware/BBB/145-digital-in.html
+++ b/hardware/BBB/145-digital-in.html
@@ -16,10 +16,10 @@
-
-
-
+
+
+
+
+
+
+
+
diff --git a/io/emoncms/88-emoncms.js b/io/emoncms/88-emoncms.js
new file mode 100644
index 00000000..a40ee9a5
--- /dev/null
+++ b/io/emoncms/88-emoncms.js
@@ -0,0 +1,71 @@
+/**
+ * Copyright 2013 Henrik Olsson henols@gmail.com
+ *
+ * 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");
+//The Server Definition - this opens (and closes) the connection
+function EmoncmsServerNode(n) {
+ RED.nodes.createNode(this,n);
+ this.server = n.server;
+ this.apikey = n.apikey;
+ this.name = n.name;
+}
+RED.nodes.registerType("emoncms-server",EmoncmsServerNode);
+
+function Emoncms(n) {
+ RED.nodes.createNode(this,n);
+ this.emonServer = n.emonServer;
+ var sc = RED.nodes.getNode(this.emonServer);
+
+ this.baseurl = sc.server;
+ this.apikey = sc.apikey;
+
+ this.topic = n.topic ||"";
+ this.nodegroup = n.nodegroup || "";
+ var node = this;
+ if (this.baseurl.substring(0,5) === "https") { var http = require("https"); }
+ else { var http = require("http"); }
+ this.on("input", function(msg) {
+
+ var topic = this.topic || msg.topic;
+ this.url = this.baseurl + '/input/post.json?json={' + topic + ':' + msg.payload+'}&apikey='+this.apikey;
+ if(this.nodegroup != ""){
+ this.url += '&node='+this.nodegroup;
+ }
+ node.log("[emoncms] "+this.url);
+ http.get(this.url, 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("emoncms",Emoncms);
diff --git a/io/emoncms/icons/emoncms-logo.png b/io/emoncms/icons/emoncms-logo.png
new file mode 100644
index 0000000000000000000000000000000000000000..8cde78715855be139d55b070f2f3bd97fb28b410
GIT binary patch
literal 1418
zcmV;51$Fv~P)(^b8FWQhbW?9;ba!ELWdL_~cP?peYja~^
zaAhuUa%Y?FJQ@H11qVq)K~zYIwU$e4RM#DczjN+$#$(SbeheOi85@%{kPzA;<4`M2
zLVyTxB1KLXRg>-#q;9fFltr3dqApr>m#S)|c2iYiL#%cMrFEJ(AvPG8$k>R7J=haG
zhVi@~_i?(gu^^&Rk;0LFyL-M*_kSM$Ba~7+%po5t{XqpIBO|S&qobh*nIpcdL`RMs
z=@vq~0X%CM=C(*AY%8V6=kqn!^)4&r|5FI@=drP|#RnD~K7908TU+b<0|VPT13??7
z=}@g!5mM3;45H~em2z3l&dx4ZtCbJN#>PJWzXT&ABb$8RKb}gZcE;oJ(Cpl0BH=LI
z$s~zH0;S;QH(72hE>bKM8Q4BRHk&Oi%wJDyn)b?x6DOAbr(kq+)T}oeGf(V%GF2!R
zP`+aDg`-^cqTKXas0)j~U4gjXU{|QZv(ekk{p%A<)4&b{RXTlPu3ReZIDPuGdxv??
zB9}^~-}I&Wx)+xg+5UsaNId^@PC0#Kv@YC06eG}ruv@q#Bb+RE@$S`rZa(!oTlxmL
zefyT!vZZ&kZQCE-&D;#$p6)I}mJX`NhG52cNv(AN--St1F&cS)p(5K~_W*!?Mv$8_l$khKV#R
zl)e^WSV+S{nl{tb2+eKJkj-XECVS#asgLgw2qA=F7@hTco#CJC!)lH4;#Qqq?GmXQ
z2+aV^0IA~&9Zwqg(g4lm3F{W!xv%*9`6Y&)9>R4TG+pn$N3ehY{(fCIbkB9^O!hMM
z=}Ep>%Fq+Gc_}664-1r#ctX-t5*JCzEb`du8Pe%AOT{J&MHefYz;#_r!!!>ZIItB!
z2XGu`k71Z$$7v!$F#;{E{PoxG&>DS&y)TZk>!*i#Jg)KCP3Y2UY$>KWcQuQKLJA2&
zB7|h7AgI-9n5Gpj6jt{D*K`0W<)eThghT?54?Ra}=MOplyI-;V^#o(*|APp$k=oqD
z&=;R`#R;Rw_MoiR_07H(UkRiX0Jad?_Kgy`t~*hwlq5J%?0@@7+UDv5Msud~aJCt
zc^;akF+Dwa%FE7tktJRmY+3e5y`}^aXrlFK#
zCAYks&1T=e!xc&?gb*5F?%uuo)kq}z+qSkgb7duWZGL|KtxP791GM#s_4A_u5Ac%7
zWM^-0@9#T0I(qV}g?eE%f9&kpv!4J>rIdHiZ7PH?1_y`!t<807*qoM6N<$f*O9OkpKVy
literal 0
HcmV?d00001
From 8bf813c9cd77fd4abad53bbb34f3a0cd99c17de0 Mon Sep 17 00:00:00 2001
From: Maxwell Hadley
Date: Sat, 8 Feb 2014 13:06:47 +0000
Subject: [PATCH 24/43] Debug & tidy up
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Make the colours the same as the RPi equivalents. Remove ‘BBB’ from the
node names.
Further debug output
---
hardware/BBB/144-analog-in.html | 89 ++++++++++++++------------------
hardware/BBB/144-analog-in.js | 46 ++++++++---------
hardware/BBB/145-digital-in.html | 54 +++++++++----------
hardware/BBB/145-digital-in.js | 54 +++++++++----------
4 files changed, 114 insertions(+), 129 deletions(-)
diff --git a/hardware/BBB/144-analog-in.html b/hardware/BBB/144-analog-in.html
index 319da5b8..7fc547a3 100644
--- a/hardware/BBB/144-analog-in.html
+++ b/hardware/BBB/144-analog-in.html
@@ -14,72 +14,59 @@
limitations under the License.
-->
-
-
-
-
-
-
-
-
+
diff --git a/hardware/BBB/144-analog-in.js b/hardware/BBB/144-analog-in.js
index bd5a6679..aa97eee1 100644
--- a/hardware/BBB/144-analog-in.js
+++ b/hardware/BBB/144-analog-in.js
@@ -19,9 +19,9 @@ var RED = require(process.env.NODE_RED_HOME+"/red/red");
// Require bonescript
try {
- var bs = require("bonescript");
-} catch(err) {
- require("util").log("[BBB-analog-in] Error: cannot find module 'bonescript'");
+ var bonescript = require("bonescript");
+} catch (err) {
+ require("util").log("[144-analog-in] Error: cannot find module 'bonescript'");
}
// The main node definition - most things happen in here
@@ -33,30 +33,28 @@ function AnalogInputNode(n) {
this.topic = n.topic;
this.pin = n.pin;
- // Define 'node' to allow us to access 'this' from within callbacks (the 'var' is essential -
- // otherwise there is only one 'node' for all instances of AnalogInputNode!)
+ // Define 'node' to allow us to access 'this' from within callbacks (the 'var' is essential -
+ // otherwise there is only one global 'node' for all instances of AnalogInputNode!)
var node = this;
- // A callback function variable seems to be more reliable than a lambda ?!
- var cbFun = function (x) {
- var msg = {};
- msg.topic = node.topic;
- msg.payload = x.value;
- if (isNaN(x.value)) {
- this.log(x.err);
- }
- node.send(msg);
- };
+ // A callback function variable seems to be more reliable than a lambda ?!
+ var readCallback = function (x) {
+ var msg = {};
+ msg.topic = node.topic;
+ msg.payload = x.value;
+ if (isNaN(x.value)) {
+ node.log(x.err);
+ }
+ node.send(msg);
+ };
- // If we have a valid pin, set the input event handler to Bonescript's analogRead
- if (["P9_39", "P9_40", "P9_37", "P9_38", "P9_33", "P9_36", "P9_35"].indexOf(node.pin) >= 0) {
- node.on("input", function (msg) { bs.analogRead(node.pin, cbFun) });
- } else {
- node.error("Unconfigured input pin");
- }
+ // If we have a valid pin, set the input event handler to Bonescript's analogRead
+ if (["P9_39", "P9_40", "P9_37", "P9_38", "P9_33", "P9_36", "P9_35"].indexOf(node.pin) >= 0) {
+ node.on("input", function (msg) { bonescript.analogRead(node.pin, readCallback) });
+ } else {
+ node.error("Unconfigured input pin");
+ }
}
// Register the node by name. This must be called before overriding any of the Node functions.
-RED.nodes.registerType("BBB-analog-in", AnalogInputNode);
-
-// AnalogInputNode.prototype.close = function () { };
+RED.nodes.registerType("analog-in", AnalogInputNode);
diff --git a/hardware/BBB/145-digital-in.html b/hardware/BBB/145-digital-in.html
index a4d2d251..8dcca48e 100644
--- a/hardware/BBB/145-digital-in.html
+++ b/hardware/BBB/145-digital-in.html
@@ -14,8 +14,7 @@
limitations under the License.
-->
-
-
+
-
+
-
-
+
diff --git a/hardware/BBB/145-digital-in.js b/hardware/BBB/145-digital-in.js
index 1d489182..886a8cde 100644
--- a/hardware/BBB/145-digital-in.js
+++ b/hardware/BBB/145-digital-in.js
@@ -19,7 +19,7 @@ var RED = require(process.env.NODE_RED_HOME + "/red/red");
// Require bonescript
try {
- var bs = require("bonescript");
+ var bonescript = require("bonescript");
} catch (err) {
require("util").log("[145-digital-in] Error: cannot find module 'bonescript'");
}
@@ -119,31 +119,31 @@ function DiscreteInputNode(n) {
"P9_15", "P9_16", "P9_17", "P9_18", "P9_21", "P9_22", "P9_23", "P9_24", "P9_26",
"P9_27", "P9_30", "P9_41", "P9_42"].indexOf(node.pin) >= 0) {
setTimeout(function () {
- bs.pinMode(node.pin, bs.INPUT);
- bs.digitalRead(node.pin, function (x) {
- // Initialise the currentState and lastActveTime variables based on the value read
- node.log("digitalRead: x.value = " + x.value);
- node.log("digitalRead: node.currentState = " + node.currentState);
- node.log("digitalRead: node.totalActiveTime = " + node.totalActiveTime);
- node.log("digitalRead: node.lastActiveTime = " + node.lastActiveTime);
- node.currentState = x.value - 0;
- node.log("First read - currentState: " + node.currentState);
- if (node.currentState === node.activeState) {
- node.lastActiveTime = Date.now();
- }
- // Attempt to attach a change-of-state interrupt handler to the pin. If we succeed,
- // set the input event and interval handlers, then send an initial message with the
- // pin state on the first output
- if (bs.attachInterrupt(node.pin, true, bs.CHANGE, interruptCallback)) {
- node.interruptAttached = true;
- node.on("input", inputCallback);
- node.intervalId = setInterval(timerCallback, node.updateInterval);
- } else {
- node.error("Failed to attach interrupt");
- }
- setTimeout(function () { node.emit("input", {}); }, 50);
- });
- }, 50);
+ bonescript.pinMode(node.pin, bonescript.INPUT);
+ bonescript.digitalRead(node.pin, function (x) {
+ // Initialise the currentState and lastActveTime variables based on the value read
+ node.log("digitalRead: x.value = " + x.value);
+ node.log("digitalRead: node.currentState = " + node.currentState);
+ node.log("digitalRead: node.totalActiveTime = " + node.totalActiveTime);
+ node.log("digitalRead: node.lastActiveTime = " + node.lastActiveTime);
+ node.currentState = x.value - 0;
+ node.log("First read - currentState: " + node.currentState);
+ if (node.currentState === node.activeState) {
+ node.lastActiveTime = Date.now();
+ }
+ // Attempt to attach a change-of-state interrupt handler to the pin. If we succeed,
+ // set the input event and interval handlers, then send an initial message with the
+ // pin state on the first output
+ if (bonescript.attachInterrupt(node.pin, true, bonescript.CHANGE, interruptCallback)) {
+ node.interruptAttached = true;
+ node.on("input", inputCallback);
+ node.intervalId = setInterval(timerCallback, node.updateInterval);
+ } else {
+ node.error("Failed to attach interrupt");
+ }
+ setTimeout(function () { node.emit("input", {}); }, 50);
+ });
+ }, 50);
} else {
node.error("Unconfigured input pin");
}
@@ -155,7 +155,7 @@ RED.nodes.registerType("discrete-in", DiscreteInputNode);
// On close, detach the interrupt (if we attached one) and clear the interval (if we set one)
DiscreteInputNode.prototype.close = function () {
if (this.interruptAttached) {
- bs.detachInterrupt(this.pin);
+ bonescript.detachInterrupt(this.pin);
}
if (this.intervalId !== null) {
clearInterval(this.intervalId);
From b1b230eb37759bae0e05b07624c2f76557b1b5e5 Mon Sep 17 00:00:00 2001
From: Dave C-J
Date: Sat, 8 Feb 2014 13:31:00 +0000
Subject: [PATCH 25/43] Forced payload to be strings for Prowl node Fixes #26
---
social/prowl/57-prowl.js | 61 ++++++++++++++++++++--------------------
1 file changed, 31 insertions(+), 30 deletions(-)
diff --git a/social/prowl/57-prowl.js b/social/prowl/57-prowl.js
index 15be6f2a..9d8801bc 100644
--- a/social/prowl/57-prowl.js
+++ b/social/prowl/57-prowl.js
@@ -24,44 +24,45 @@ var util = require('util');
// module.exports = {prowlkey:'My-API-KEY'}
try {
- var pushkey = RED.settings.prowl || require(process.env.NODE_RED_HOME+"/../pushkey.js");
+ var pushkey = RED.settings.prowl || require(process.env.NODE_RED_HOME+"/../pushkey.js");
}
catch(err) {
- util.log("[57-prowl.js] Error: Failed to load Prowl credentials");
+ util.log("[57-prowl.js] Error: Failed to load Prowl credentials");
}
if (pushkey) {
- var prowl = new Prowl(pushkey.prowlkey);
+ var prowl = new Prowl(pushkey.prowlkey);
}
function ProwlNode(n) {
- RED.nodes.createNode(this,n);
- this.title = n.title;
- this.priority = parseInt(n.priority);
- if (this.priority > 2) this.priority = 2;
- if (this.priority < -2) this.priority = -2;
- var node = this;
- this.on("input",function(msg) {
- var titl = this.title||msg.topic||"Node-RED";
- var pri = msg.priority||this.priority;
- if (typeof(msg.payload) == 'object') {
- msg.payload = JSON.stringify(msg.payload);
- }
- if (pushkey) {
- try {
- prowl.push(msg.payload, titl, { priority: pri }, function(err, remaining) {
- if (err) node.error(err);
- node.log( remaining + ' calls to Prowl api during current hour.' );
- });
- }
- catch (err) {
- node.error(err);
- }
- }
- else {
- node.warn("Prowl credentials not set/found. See node info.");
- }
- });
+ RED.nodes.createNode(this,n);
+ this.title = n.title;
+ this.priority = parseInt(n.priority);
+ if (this.priority > 2) this.priority = 2;
+ if (this.priority < -2) this.priority = -2;
+ var node = this;
+ this.on("input",function(msg) {
+ var titl = this.title||msg.topic||"Node-RED";
+ var pri = msg.priority||this.priority;
+ if (typeof(msg.payload) == 'object') {
+ msg.payload = JSON.stringify(msg.payload);
+ }
+ else { msg.payload = msg.payload.toString(); }
+ if (pushkey) {
+ try {
+ prowl.push(msg.payload, titl, { priority: pri }, function(err, remaining) {
+ if (err) node.error(err);
+ node.log( remaining + ' calls to Prowl api during current hour.' );
+ });
+ }
+ catch (err) {
+ node.error(err);
+ }
+ }
+ else {
+ node.warn("Prowl credentials not set/found. See node info.");
+ }
+ });
}
RED.nodes.registerType("prowl",ProwlNode);
From c6108cffb45123f9259f473b5421024ec520b055 Mon Sep 17 00:00:00 2001
From: Maxwell Hadley
Date: Sat, 8 Feb 2014 22:35:00 +0000
Subject: [PATCH 26/43] Polishing discrete-in a bit
Commented out debug: added numeric validator
---
hardware/BBB/145-digital-in.html | 2 +-
hardware/BBB/145-digital-in.js | 36 ++++++++++++++++----------------
2 files changed, 19 insertions(+), 19 deletions(-)
diff --git a/hardware/BBB/145-digital-in.html b/hardware/BBB/145-digital-in.html
index 8dcca48e..1beb2c70 100644
--- a/hardware/BBB/145-digital-in.html
+++ b/hardware/BBB/145-digital-in.html
@@ -95,7 +95,7 @@ of the active time, not the pin state sent on the first output
color:"#c6dbef",
defaults: { // defines the editable properties of the node
name: { value:"" }, // along with default values.
- updateInterval: { value:60 },
+ updateInterval: { value:60, required:true, validate:RED.validators.number() },
topic: { value:"", required:true },
pin: { value:"", required:true },
activeLow: { value:false, required:true}
diff --git a/hardware/BBB/145-digital-in.js b/hardware/BBB/145-digital-in.js
index 886a8cde..79e8b2a6 100644
--- a/hardware/BBB/145-digital-in.js
+++ b/hardware/BBB/145-digital-in.js
@@ -53,10 +53,10 @@ function DiscreteInputNode(n) {
// Note: this function gets called spuriously when the interrupt is first enabled: in this
// case x.value is undefined - we must test for this
var interruptCallback = function (x) {
- node.log("interruptCallback: x.value = " + x.value);
- node.log("interruptCallback: node.currentState = " + node.currentState);
- node.log("interruptCallback: node.totalActiveTime = " + node.totalActiveTime);
- node.log("interruptCallback: node.lastActiveTime = " + node.lastActiveTime);
+// node.log("interruptCallback: x.value = " + x.value);
+// node.log("interruptCallback: node.currentState = " + node.currentState);
+// node.log("interruptCallback: node.totalActiveTime = " + node.totalActiveTime);
+// node.log("interruptCallback: node.lastActiveTime = " + node.lastActiveTime);
if (node.currentState === x.value - 0) {
node.log("Spurious interrupt: " + x.value);
} else if (x.value != undefined) {
@@ -77,9 +77,9 @@ function DiscreteInputNode(n) {
// This function is called by the timer. It updates the ActiveTime variables, and sends a
// message on the second output with the latest value of the total active time, in seconds
var timerCallback = function () {
- node.log("timerCallback: node.currentState = " + node.currentState);
- node.log("timerCallback: node.totalActiveTime = " + node.totalActiveTime);
- node.log("timerCallback: node.lastActiveTime = " + node.lastActiveTime);
+// node.log("timerCallback: node.currentState = " + node.currentState);
+// node.log("timerCallback: node.totalActiveTime = " + node.totalActiveTime);
+// node.log("timerCallback: node.lastActiveTime = " + node.lastActiveTime);
if (node.currentState === node.activeState) {
var now = Date.now();
node.totalActiveTime += now - node.lastActiveTime;
@@ -94,9 +94,9 @@ function DiscreteInputNode(n) {
// This function is called when we receive an input message. Clear the ActiveTime variables
// (so we start counting from zero again)
var inputCallback = function (msg) {
- node.log("inputCallback: node.currentState = " + node.currentState);
- node.log("inputCallback: node.totalActiveTime = " + node.totalActiveTime);
- node.log("inputCallback: node.lastActiveTime = " + node.lastActiveTime);
+// node.log("inputCallback: node.currentState = " + node.currentState);
+// node.log("inputCallback: node.totalActiveTime = " + node.totalActiveTime);
+// node.log("inputCallback: node.lastActiveTime = " + node.lastActiveTime);
node.totalActiveTime = 0;
if (node.currentState === node.activeState) {
node.lastActiveTime = Date.now();
@@ -107,9 +107,9 @@ function DiscreteInputNode(n) {
msg[0].payload = node.currentState;
msg[1].payload = node.totalActiveTime;
node.send(msg);
- node.log("Initial message: " + msg[0].payload + " " + msg[1].payload);
- node.log("currentState: " + node.currentState);
- node.log("activeTime: " + node.totalActiveTime);
+// node.log("Initial message: " + msg[0].payload + " " + msg[1].payload);
+// node.log("currentState: " + node.currentState);
+// node.log("activeTime: " + node.totalActiveTime);
}
};
@@ -122,12 +122,12 @@ function DiscreteInputNode(n) {
bonescript.pinMode(node.pin, bonescript.INPUT);
bonescript.digitalRead(node.pin, function (x) {
// Initialise the currentState and lastActveTime variables based on the value read
- node.log("digitalRead: x.value = " + x.value);
- node.log("digitalRead: node.currentState = " + node.currentState);
- node.log("digitalRead: node.totalActiveTime = " + node.totalActiveTime);
- node.log("digitalRead: node.lastActiveTime = " + node.lastActiveTime);
+// node.log("digitalRead: x.value = " + x.value);
+// node.log("digitalRead: node.currentState = " + node.currentState);
+// node.log("digitalRead: node.totalActiveTime = " + node.totalActiveTime);
+// node.log("digitalRead: node.lastActiveTime = " + node.lastActiveTime);
node.currentState = x.value - 0;
- node.log("First read - currentState: " + node.currentState);
+// node.log("First read - currentState: " + node.currentState);
if (node.currentState === node.activeState) {
node.lastActiveTime = Date.now();
}
From 68080df45eb13009309d03ab0d1601a2059f7453 Mon Sep 17 00:00:00 2001
From: Maxwell Hadley
Date: Sat, 8 Feb 2014 22:36:23 +0000
Subject: [PATCH 27/43] Starting to add scale factors & linearisation
Added breakpoints property to the node
Building the breakpoints editor (nearly there!)
---
hardware/BBB/144-analog-in.html | 88 ++++++++++++++++++++++++++++++++-
hardware/BBB/144-analog-in.js | 6 +++
2 files changed, 93 insertions(+), 1 deletion(-)
diff --git a/hardware/BBB/144-analog-in.html b/hardware/BBB/144-analog-in.html
index 7fc547a3..98739ef7 100644
--- a/hardware/BBB/144-analog-in.html
+++ b/hardware/BBB/144-analog-in.html
@@ -37,6 +37,16 @@
@@ -58,6 +68,7 @@ analogue input in the range [0-1), or NaN if an read error occurs (errors are lo
name: { value:"" }, // along with default values.
topic: { value:"", required:true },
pin: { value:"", required:true },
+ breakpoints: { value:[{input:0.0, output:0.0, mutable:false}, {input:1.0, output:1.0, mutable:false}] }
},
inputs:1, // set the number of inputs - only 0 or 1
outputs:1, // set the number of outputs - 0 to n
@@ -67,6 +78,81 @@ analogue input in the range [0-1), or NaN if an read error occurs (errors are lo
},
labelStyle: function() { // sets the class to apply to the label
return this.name ? "node_label_italic" : "";
- }
+ },
+ oneditprepare: function () {
+ function generateBreakpoint(i, breakpoint) {
+ var container = $('', {style:"margin:0; padding:8px 0px; border-bottom: 1px solid #ccc;"});
+ var row = $('').appendTo(container);
+ var row2 = $('', {style:"padding-top: 5px; text-align: right;"}).appendTo(container);
+
+ var breakpointField = $('').appendTo(row);
+ var inputValueField = $('',
+ {class:"node-input-breakpoint-input-value", type:"text", style:"margin-left:5px; margin-right:2px; width:36%;"}).appendTo(breakpointField);
+ breakpointField.append(" => ");
+ var outputValueField = $('',
+ {class:"node-input-breakpoint-output-value", type:"text", style:"margin-left:0px; width:36%;"}).appendTo(breakpointField);
+ var finalSpan = $('', {style:"float:right; margin-top:3px; margin-right:10px;"}).appendTo(row);
+ var mutableFlag = $('', {class:"node-input-breakpoint-mutable", style:"display:hide;"}).appendTo(row);
+ if (breakpoint.mutable) {
+ mutableFlag.attr("mutable", "true");
+ var deleteButton = $('', {href:"#", class:"btn btn-mini", style:"margin-left: 5px;"}).appendTo(finalSpan);
+ $('', {class:"icon-remove"}).appendTo(deleteButton);
+
+ deleteButton.click(function() {
+ container.css({"background":"#fee"});
+ container.fadeOut(300, function() {
+ $(this).remove();
+ $("#node-input-breakpoint-container").children().each(function (i) {
+ $(this).find(".node-input-breakpoint-index").html(i + 1);
+ });
+ });
+ });
+ } else {
+ mutableFlag.attr("mutable", "false");
+ }
+ inputValueField.val(breakpoint.input);
+ outputValueField.val(breakpoint.output);
+ $("#node-input-breakpoint-container").append(container);
+ }
+
+ $("#node-input-add-breakpoint").click(function () {
+ generateBreakpoint($("#node-input-breakpoint-container").children().length + 1, {input:0, output:0, mutable:true});
+ $("#node-input-breakpoint-container-div").scrollTop($("#node-input-breakpoint-container-div").get(0).scrollHeight);
+ });
+
+ for (var i = 0; i < this.breakpoints.length; i++) {
+ var breakpoint = this.breakpoints[i];
+ generateBreakpoint(i + 1, breakpoint);
+ }
+
+ // Handle resizing the Input Scaling div when the dialog is resized
+ function switchDialogResize(ev, ui) {
+ $("#node-input-breakpoint-container-div").css("height", (ui.size.height - 290) + "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 breakpoints = $("#node-input-breakpoint-container").children();
+ var node = this;
+ node.breakpoints = [];
+ breakpoints.each(function (i) {
+ var breakpoint = $(this);
+ var r = {};
+ r.input = breakpoint.find(".node-input-breakpoint-input-value").val() - 0
+ r.output = breakpoint.find(".node-input-breakpoint-output-value").val() - 0;
+ r.mutable = breakpoint.find(".node-input-breakpoint-mutable").attr("mutable") == "true";
+ node.breakpoints.push(r);
+ });
+ }
});
diff --git a/hardware/BBB/144-analog-in.js b/hardware/BBB/144-analog-in.js
index aa97eee1..36223b76 100644
--- a/hardware/BBB/144-analog-in.js
+++ b/hardware/BBB/144-analog-in.js
@@ -32,11 +32,17 @@ function AnalogInputNode(n) {
// Store local copies of the node configuration (as defined in the .html)
this.topic = n.topic;
this.pin = n.pin;
+ this.breakpoints = n.breakpoints;
// Define 'node' to allow us to access 'this' from within callbacks (the 'var' is essential -
// otherwise there is only one global 'node' for all instances of AnalogInputNode!)
var node = this;
+ node.log("breakpoints:");
+ for (var i = 0; i < node.breakpoints.length; i++) {
+ node.log(i + ": {input:" + node.breakpoints[i].input + ", output:" + node.breakpoints[i].output + ", mutable:" + node.breakpoints[i].mutable +"}");
+ }
+
// A callback function variable seems to be more reliable than a lambda ?!
var readCallback = function (x) {
var msg = {};
From dc13290f97e227076fdcd4517b67ee2c5665e62b Mon Sep 17 00:00:00 2001
From: henols
Date: Sun, 9 Feb 2014 00:21:17 +0100
Subject: [PATCH 28/43] Some usage clarifications
---
io/emoncms/88-emoncms.html | 15 ++++++++-------
io/emoncms/88-emoncms.js | 5 +++--
2 files changed, 11 insertions(+), 9 deletions(-)
diff --git a/io/emoncms/88-emoncms.html b/io/emoncms/88-emoncms.html
index ff71828b..b179b7af 100644
--- a/io/emoncms/88-emoncms.html
+++ b/io/emoncms/88-emoncms.html
@@ -23,21 +23,22 @@
+
+
+
+
-
-
-
-
-
If Topic is left blank the output Topic is the same as the input Topic.
+
Topic is not mandatory, if Topic is left blank msg.topic will used. Topic overrides msg.topic
+ Node Group (numeric) is not mandatory, if Node Group is left blank msg.nodegrpup will used. Node Group overrides msg.nodegroup
@@ -55,7 +55,13 @@
Analogue input for the Beaglebone Black. Reads an anlogue pin when triggered
The output message topic is the node topic: the output message value is the
-analogue input in the range [0-1), or NaN if an read error occurs (errors are logged)
+scaled analogue input or NaN if an read error occurs (errors are logged).
+
+
+Simple linear scaling is defined by setting the output values required for input
+values of 0 and 1. You can apply more complicated scaling, such as linearisation,
+by defining breakpoints at intermediate input values, with the desired output for
+each. Intermediate values are linearly interpolated.
@@ -80,79 +86,88 @@ analogue input in the range [0-1), or NaN if an read error occurs (errors are lo
return this.name ? "node_label_italic" : "";
},
oneditprepare: function () {
- function generateBreakpoint(i, breakpoint) {
- var container = $('', {style:"margin:0; padding:8px 0px; border-bottom: 1px solid #ccc;"});
- var row = $('').appendTo(container);
- var row2 = $('', {style:"padding-top: 5px; text-align: right;"}).appendTo(container);
-
- var breakpointField = $('').appendTo(row);
+ function generateBreakpoint(breakpoint, insert) {
+ var container = $('', {style:"margin:0; padding:8px 0px; border-bottom: 1px solid #ccc;"});
+ var row = $('').appendTo(container);
+ var breakpointField = $('').appendTo(row);
var inputValueField = $('',
- {class:"node-input-breakpoint-input-value", type:"text", style:"margin-left:5px; margin-right:2px; width:36%;"}).appendTo(breakpointField);
- breakpointField.append(" => ");
- var outputValueField = $('',
- {class:"node-input-breakpoint-output-value", type:"text", style:"margin-left:0px; width:36%;"}).appendTo(breakpointField);
- var finalSpan = $('', {style:"float:right; margin-top:3px; margin-right:10px;"}).appendTo(row);
- var mutableFlag = $('', {class:"node-input-breakpoint-mutable", style:"display:hide;"}).appendTo(row);
- if (breakpoint.mutable) {
- mutableFlag.attr("mutable", "true");
- var deleteButton = $('', {href:"#", class:"btn btn-mini", style:"margin-left: 5px;"}).appendTo(finalSpan);
- $('', {class:"icon-remove"}).appendTo(deleteButton);
+ {disabled:"", class:"node-input-breakpoint-input-value", type:"text", style:"margin-left:5px; margin-right:2px; width:36%;"}).appendTo(breakpointField);
+ if (breakpoint.mutable) {
+ inputValueField.removeAttr("disabled");
+ }
+ breakpointField.append(" => ");
+ var outputValueField = $('',
+ {class:"node-input-breakpoint-output-value", type:"text", style:"margin-left:0px; width:36%;"}).appendTo(breakpointField);
+ var finalSpan = $('', {style:"float:right; margin-top:3px; margin-right:10px;"}).appendTo(row);
+ var mutableFlag = $('', {class:"node-input-breakpoint-mutable", style:"display:hide;"}).appendTo(row);
+ if (breakpoint.mutable) {
+ mutableFlag.attr("mutable", "true");
+ var deleteButton = $('', {href:"#", class:"btn btn-mini", style:"margin-left: 5px;"}).appendTo(finalSpan);
+ $('', {class:"icon-remove"}).appendTo(deleteButton);
- deleteButton.click(function() {
- container.css({"background":"#fee"});
- container.fadeOut(300, function() {
- $(this).remove();
- $("#node-input-breakpoint-container").children().each(function (i) {
- $(this).find(".node-input-breakpoint-index").html(i + 1);
- });
- });
- });
- } else {
- mutableFlag.attr("mutable", "false");
- }
- inputValueField.val(breakpoint.input);
- outputValueField.val(breakpoint.output);
- $("#node-input-breakpoint-container").append(container);
- }
-
- $("#node-input-add-breakpoint").click(function () {
- generateBreakpoint($("#node-input-breakpoint-container").children().length + 1, {input:0, output:0, mutable:true});
- $("#node-input-breakpoint-container-div").scrollTop($("#node-input-breakpoint-container-div").get(0).scrollHeight);
- });
-
- for (var i = 0; i < this.breakpoints.length; i++) {
- var breakpoint = this.breakpoints[i];
- generateBreakpoint(i + 1, breakpoint);
- }
-
- // Handle resizing the Input Scaling div when the dialog is resized
- function switchDialogResize(ev, ui) {
- $("#node-input-breakpoint-container-div").css("height", (ui.size.height - 290) + "px");
- };
+ deleteButton.click(function() {
+ container.css({"background":"#fee"});
+ container.fadeOut(300, function() {
+ $(this).remove();
+ });
+ });
+ } else {
+ mutableFlag.attr("mutable", "false");
+ }
+ if (insert === true) {
+ var last = $("#node-input-breakpoint-container").children().last();
+ var prev = last.prev();
+ inputValueField.val(((last.find(".node-input-breakpoint-input-value").val() - 0) +
+ (prev.find(".node-input-breakpoint-input-value").val() - 0))/2);
+ outputValueField.val(((last.find(".node-input-breakpoint-output-value").val() - 0) +
+ (prev.find(".node-input-breakpoint-output-value").val() - 0))/2);
+ last.before(container);
+ } else {
+ inputValueField.val(breakpoint.input);
+ outputValueField.val(breakpoint.output);
+ $("#node-input-breakpoint-container").append(container);
+ }
+ }
+
+ $("#node-input-add-breakpoint").click(function () {
+ generateBreakpoint({input:0, output:0, mutable:true}, true);
+ $("#node-input-breakpoint-container-div").scrollTop($("#node-input-breakpoint-container-div").get(0).scrollHeight);
+ });
+
+ for (var i = 0; i < this.breakpoints.length; i++) {
+ var breakpoint = this.breakpoints[i];
+ generateBreakpoint(breakpoint, false);
+ }
+
+ // Handle resizing the Input Scaling div when the dialog is resized
+ function switchDialogResize(ev, ui) {
+ $("#node-input-breakpoint-container-div").css("height", (ui.size.height - 290) + "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);
- });
- },
+ $("#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 breakpoints = $("#node-input-breakpoint-container").children();
- var node = this;
- node.breakpoints = [];
- breakpoints.each(function (i) {
- var breakpoint = $(this);
- var r = {};
- r.input = breakpoint.find(".node-input-breakpoint-input-value").val() - 0
- r.output = breakpoint.find(".node-input-breakpoint-output-value").val() - 0;
- r.mutable = breakpoint.find(".node-input-breakpoint-mutable").attr("mutable") == "true";
- node.breakpoints.push(r);
- });
- }
+ var breakpoints = $("#node-input-breakpoint-container").children();
+ var node = this;
+ node.breakpoints = [];
+ breakpoints.each(function (i) {
+ var breakpoint = $(this);
+ var r = {};
+ r.input = breakpoint.find(".node-input-breakpoint-input-value").val() - 0
+ r.output = breakpoint.find(".node-input-breakpoint-output-value").val() - 0;
+ r.mutable = breakpoint.find(".node-input-breakpoint-mutable").attr("mutable") == "true";
+ node.breakpoints.push(r);
+ });
+ node.breakpoints = node.breakpoints.sort(function (a, b) { return a.input - b.input; });
+ }
});
From f64358262a02856fa09df52135363843d9848cbb Mon Sep 17 00:00:00 2001
From: Dave C-J
Date: Sun, 9 Feb 2014 11:55:01 +0000
Subject: [PATCH 30/43] Updates to Twilio Node Fixes #28 Make edit panel work,
and then fix it so the things edited do what they say. Slight tweak to shrink
icon to similar size as others.
---
social/twilio/56-twilio.html | 23 +++++++-------
social/twilio/56-twilio.js | 56 ++++++++++++++++-----------------
social/twilio/icons/twilio.png | Bin 717 -> 553 bytes
3 files changed, 40 insertions(+), 39 deletions(-)
diff --git a/social/twilio/56-twilio.html b/social/twilio/56-twilio.html
index 21badcb7..c73769bf 100644
--- a/social/twilio/56-twilio.html
+++ b/social/twilio/56-twilio.html
@@ -15,31 +15,32 @@
limitations under the License.
-->
-
diff --git a/io/emoncms/88-emoncms.js b/io/emoncms/88-emoncms.js
index a007d3d5..74ad43a4 100644
--- a/io/emoncms/88-emoncms.js
+++ b/io/emoncms/88-emoncms.js
@@ -19,11 +19,50 @@ var RED = require(process.env.NODE_RED_HOME+"/red/red");
function EmoncmsServerNode(n) {
RED.nodes.createNode(this,n);
this.server = n.server;
- this.apikey = n.apikey;
this.name = n.name;
+ var credentials = RED.nodes.getCredentials(n.id);
+ if (credentials) {
+ this.apikey = credentials.apikey;
+ }
+
}
RED.nodes.registerType("emoncms-server",EmoncmsServerNode);
+var querystring = require('querystring');
+
+RED.app.get('/emoncms-server/:id',function(req,res) {
+ var credentials = RED.nodes.getCredentials(req.params.id);
+ if (credentials) {
+ res.send(JSON.stringify({apikey:credentials.apikey}));
+ } else {
+ res.send(JSON.stringify({}));
+ }
+});
+
+RED.app.delete('/emoncms-server/:id',function(req,res) {
+ RED.nodes.deleteCredentials(req.params.id);
+ res.send(200);
+});
+
+RED.app.post('/emoncms-server/:id',function(req,res) {
+
+ var body = "";
+ req.on('data', function(chunk) {
+ body+=chunk;
+ });
+ req.on('end', function(){
+ var newCreds = querystring.parse(body);
+ var credentials = RED.nodes.getCredentials(req.params.id)||{};
+ if (newCreds.apikey == null || newCreds.apikey == "") {
+ delete credentials.apikey;
+ } else {
+ credentials.apikey = newCreds.apikey;
+ }
+ RED.nodes.addCredentials(req.params.id,credentials);
+ res.send(200);
+ });
+});
+
function Emoncms(n) {
RED.nodes.createNode(this,n);
this.emonServer = n.emonServer;
From f46b59d69f053a2d8b1727f297429dfa24b01959 Mon Sep 17 00:00:00 2001
From: Maxwell Hadley
Date: Wed, 12 Feb 2014 22:27:08 +0000
Subject: [PATCH 32/43] Added analogue scaling & averaging; discrete check
reads
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Added input scaling function and averaging of multiple readings for
noise reduction to the analogue input node.
Added the ‘sanity check’ read of the digital input pin to the timer
callback in the discrete input node
---
hardware/BBB/144-analog-in.html | 38 ++++++++++++++++----------
hardware/BBB/144-analog-in.js | 47 +++++++++++++++++++++++----------
hardware/BBB/145-digital-in.js | 33 ++++++-----------------
3 files changed, 65 insertions(+), 53 deletions(-)
diff --git a/hardware/BBB/144-analog-in.html b/hardware/BBB/144-analog-in.html
index 6d053f23..24573e2f 100644
--- a/hardware/BBB/144-analog-in.html
+++ b/hardware/BBB/144-analog-in.html
@@ -37,11 +37,16 @@
Input Scaling
@@ -52,17 +57,21 @@
@@ -74,7 +83,8 @@ each. Intermediate values are linearly interpolated.
name: { value:"" }, // along with default values.
topic: { value:"", required:true },
pin: { value:"", required:true },
- breakpoints: { value:[{input:0.0, output:0.0, mutable:false}, {input:1.0, output:1.0, mutable:false}] }
+ breakpoints: { value:[{input:0.0, output:0.0, mutable:false}, {input:1.0, output:1.0, mutable:false}] },
+ averaging: { value:false, required:true }
},
inputs:1, // set the number of inputs - only 0 or 1
outputs:1, // set the number of outputs - 0 to n
@@ -87,7 +97,7 @@ each. Intermediate values are linearly interpolated.
},
oneditprepare: function () {
function generateBreakpoint(breakpoint, insert) {
- var container = $('', {style:"margin:0; padding:8px 0px; border-bottom: 1px solid #ccc;"});
+ var container = $('', {style:"margin:0; padding:4px; padding-left 10px;"});
var row = $('').appendTo(container);
var breakpointField = $('').appendTo(row);
var inputValueField = $('',
@@ -117,10 +127,10 @@ each. Intermediate values are linearly interpolated.
if (insert === true) {
var last = $("#node-input-breakpoint-container").children().last();
var prev = last.prev();
- inputValueField.val(((last.find(".node-input-breakpoint-input-value").val() - 0) +
- (prev.find(".node-input-breakpoint-input-value").val() - 0))/2);
- outputValueField.val(((last.find(".node-input-breakpoint-output-value").val() - 0) +
- (prev.find(".node-input-breakpoint-output-value").val() - 0))/2);
+ inputValueField.val((Number(last.find(".node-input-breakpoint-input-value").val()) +
+ Number(prev.find(".node-input-breakpoint-input-value").val()))/2);
+ outputValueField.val((Number(last.find(".node-input-breakpoint-output-value").val()) +
+ Number(prev.find(".node-input-breakpoint-output-value").val()))/2);
last.before(container);
} else {
inputValueField.val(breakpoint.input);
@@ -139,9 +149,9 @@ each. Intermediate values are linearly interpolated.
generateBreakpoint(breakpoint, false);
}
- // Handle resizing the Input Scaling div when the dialog is resized
+ // Handle resizing the Input Scaling div when the dialog is resized - this isn't quite right!
function switchDialogResize(ev, ui) {
- $("#node-input-breakpoint-container-div").css("height", (ui.size.height - 290) + "px");
+ $("#node-input-breakpoint-container-div").css("height", (ui.size.height - 299) + "px");
};
$("#dialog").on("dialogresize", switchDialogResize);
@@ -162,8 +172,8 @@ each. Intermediate values are linearly interpolated.
breakpoints.each(function (i) {
var breakpoint = $(this);
var r = {};
- r.input = breakpoint.find(".node-input-breakpoint-input-value").val() - 0
- r.output = breakpoint.find(".node-input-breakpoint-output-value").val() - 0;
+ r.input = Number(breakpoint.find(".node-input-breakpoint-input-value").val());
+ r.output = Number(breakpoint.find(".node-input-breakpoint-output-value").val());
r.mutable = breakpoint.find(".node-input-breakpoint-mutable").attr("mutable") == "true";
node.breakpoints.push(r);
});
diff --git a/hardware/BBB/144-analog-in.js b/hardware/BBB/144-analog-in.js
index 36223b76..e9ff8995 100644
--- a/hardware/BBB/144-analog-in.js
+++ b/hardware/BBB/144-analog-in.js
@@ -33,30 +33,49 @@ function AnalogInputNode(n) {
this.topic = n.topic;
this.pin = n.pin;
this.breakpoints = n.breakpoints;
+ this.averaging = n.averaging;
+ if (this.averaging) {
+ this.averages = 10;
+ } else {
+ this.averages = 1;
+ }
// Define 'node' to allow us to access 'this' from within callbacks (the 'var' is essential -
// otherwise there is only one global 'node' for all instances of AnalogInputNode!)
var node = this;
- node.log("breakpoints:");
- for (var i = 0; i < node.breakpoints.length; i++) {
- node.log(i + ": {input:" + node.breakpoints[i].input + ", output:" + node.breakpoints[i].output + ", mutable:" + node.breakpoints[i].mutable +"}");
- }
-
- // A callback function variable seems to be more reliable than a lambda ?!
- var readCallback = function (x) {
- var msg = {};
- msg.topic = node.topic;
- msg.payload = x.value;
- if (isNaN(x.value)) {
- node.log(x.err);
+ // Variables used for input averaging
+ var sum; // accumulates the input readings to be averaged
+ var count; // keep track of the number of measurements made
+
+ // The callback function for analogRead. Accumulates the required number of
+ // measurements, then divides the total number, applies output scaling and
+ // sends the result
+ var analogReadCallback = function (x) {
+ sum = sum + x.value;
+ count = count - 1;
+ if (count > 0) {
+ bonescript.analogRead(node.pin, analogReadCallback);
+ } else {
+ var msg = {};
+ msg.topic = node.topic;
+ sum = sum/node.averages;
+ // i is the index of the first breakpoint where the 'input' value is strictly
+ // greater than the measurement (note: a measurement can never be == 1)
+ var i = node.breakpoints.map(function (breakpoint) { return sum >= breakpoint.input; }).indexOf(false);
+ msg.payload = node.breakpoints[i-1].output + (node.breakpoints[i].output - node.breakpoints[i-1].output) *
+ (sum - node.breakpoints[i-1].input)/(node.breakpoints[i].input - node.breakpoints[i-1].input);
+ node.send(msg);
}
- node.send(msg);
};
// If we have a valid pin, set the input event handler to Bonescript's analogRead
if (["P9_39", "P9_40", "P9_37", "P9_38", "P9_33", "P9_36", "P9_35"].indexOf(node.pin) >= 0) {
- node.on("input", function (msg) { bonescript.analogRead(node.pin, readCallback) });
+ node.on("input", function (msg) {
+ sum = 0;
+ count = node.averages;
+ bonescript.analogRead(node.pin, analogReadCallback);
+ });
} else {
node.error("Unconfigured input pin");
}
diff --git a/hardware/BBB/145-digital-in.js b/hardware/BBB/145-digital-in.js
index 79e8b2a6..6bb59dbb 100644
--- a/hardware/BBB/145-digital-in.js
+++ b/hardware/BBB/145-digital-in.js
@@ -53,14 +53,8 @@ function DiscreteInputNode(n) {
// Note: this function gets called spuriously when the interrupt is first enabled: in this
// case x.value is undefined - we must test for this
var interruptCallback = function (x) {
-// node.log("interruptCallback: x.value = " + x.value);
-// node.log("interruptCallback: node.currentState = " + node.currentState);
-// node.log("interruptCallback: node.totalActiveTime = " + node.totalActiveTime);
-// node.log("interruptCallback: node.lastActiveTime = " + node.lastActiveTime);
- if (node.currentState === x.value - 0) {
- node.log("Spurious interrupt: " + x.value);
- } else if (x.value != undefined) {
- node.currentState = x.value - 0;
+ if (x.value != undefined && node.currentState !== Number(x.value)) {
+ node.currentState = Number(x.value);
var now = Date.now();
if (node.currentState === node.activeState) {
node.lastActiveTime = now;
@@ -77,9 +71,6 @@ function DiscreteInputNode(n) {
// This function is called by the timer. It updates the ActiveTime variables, and sends a
// message on the second output with the latest value of the total active time, in seconds
var timerCallback = function () {
-// node.log("timerCallback: node.currentState = " + node.currentState);
-// node.log("timerCallback: node.totalActiveTime = " + node.totalActiveTime);
-// node.log("timerCallback: node.lastActiveTime = " + node.lastActiveTime);
if (node.currentState === node.activeState) {
var now = Date.now();
node.totalActiveTime += now - node.lastActiveTime;
@@ -89,14 +80,13 @@ function DiscreteInputNode(n) {
msg.topic = node.topic;
msg.payload = node.totalActiveTime / 1000;
node.send([null, msg]);
+ // Re-synchronise the pin state if we have missed a state change interrupt for some reason
+ bonescript.digitalRead(node.pin, interruptCallback);
};
// This function is called when we receive an input message. Clear the ActiveTime variables
// (so we start counting from zero again)
var inputCallback = function (msg) {
-// node.log("inputCallback: node.currentState = " + node.currentState);
-// node.log("inputCallback: node.totalActiveTime = " + node.totalActiveTime);
-// node.log("inputCallback: node.lastActiveTime = " + node.lastActiveTime);
node.totalActiveTime = 0;
if (node.currentState === node.activeState) {
node.lastActiveTime = Date.now();
@@ -107,9 +97,6 @@ function DiscreteInputNode(n) {
msg[0].payload = node.currentState;
msg[1].payload = node.totalActiveTime;
node.send(msg);
-// node.log("Initial message: " + msg[0].payload + " " + msg[1].payload);
-// node.log("currentState: " + node.currentState);
-// node.log("activeTime: " + node.totalActiveTime);
}
};
@@ -118,16 +105,12 @@ function DiscreteInputNode(n) {
"P8_16", "P8_17", "P8_18", "P8_19", "P8_26", "P9_11", "P9_12", "P9_13", "P9_14",
"P9_15", "P9_16", "P9_17", "P9_18", "P9_21", "P9_22", "P9_23", "P9_24", "P9_26",
"P9_27", "P9_30", "P9_41", "P9_42"].indexOf(node.pin) >= 0) {
- setTimeout(function () {
+ // Don't set up interrupts & intervals until after the close event handler has been installed
+ process.nextTick(function () {
bonescript.pinMode(node.pin, bonescript.INPUT);
bonescript.digitalRead(node.pin, function (x) {
// Initialise the currentState and lastActveTime variables based on the value read
-// node.log("digitalRead: x.value = " + x.value);
-// node.log("digitalRead: node.currentState = " + node.currentState);
-// node.log("digitalRead: node.totalActiveTime = " + node.totalActiveTime);
-// node.log("digitalRead: node.lastActiveTime = " + node.lastActiveTime);
- node.currentState = x.value - 0;
-// node.log("First read - currentState: " + node.currentState);
+ node.currentState = Number(x.value);
if (node.currentState === node.activeState) {
node.lastActiveTime = Date.now();
}
@@ -143,7 +126,7 @@ function DiscreteInputNode(n) {
}
setTimeout(function () { node.emit("input", {}); }, 50);
});
- }, 50);
+ });
} else {
node.error("Unconfigured input pin");
}
From 4a53c20792ebb4220fcbf534cfe804f7f849d292 Mon Sep 17 00:00:00 2001
From: Dave C-J
Date: Fri, 14 Feb 2014 20:38:39 +0000
Subject: [PATCH 33/43] Update pushbullet to use latest 0.4 npm and allow iden
as well as id
---
social/pushbullet/57-pushbullet.html | 13 ++++---
social/pushbullet/57-pushbullet.js | 55 ++++++++++++++--------------
2 files changed, 35 insertions(+), 33 deletions(-)
diff --git a/social/pushbullet/57-pushbullet.html b/social/pushbullet/57-pushbullet.html
index 32020297..dda75be5 100644
--- a/social/pushbullet/57-pushbullet.html
+++ b/social/pushbullet/57-pushbullet.html
@@ -26,12 +26,13 @@
-
-
+
-
+
+
+
+
+
+
+
+
+
+
+
diff --git a/hardware/BBB/145-digital-in.js b/hardware/BBB/145-digital-in.js
index 6bb59dbb..2d928728 100644
--- a/hardware/BBB/145-digital-in.js
+++ b/hardware/BBB/145-digital-in.js
@@ -24,7 +24,7 @@ try {
require("util").log("[145-digital-in] Error: cannot find module 'bonescript'");
}
-// The node constructor
+// discrete-in node constructor
function DiscreteInputNode(n) {
RED.nodes.createNode(this, n);
@@ -36,38 +36,68 @@ function DiscreteInputNode(n) {
else
this.activeState = 1;
this.updateInterval = n.updateInterval * 1000; // How often to send totalActiveTime messages
+ this.debounce = n.debounce; // Enable switch contact debouncing algorithm
this.interruptAttached = false; // Flag: should we detach interrupt when we are closed?
this.intervalId = null; // Remember the timer ID so we can delete it when we are closed
this.currentState = 0; // The pin input state "1" or "0"
this.lastActiveTime = NaN; // The date (in ms since epoch) when the pin last went high
+ // switch to process.hrtime()
this.totalActiveTime = 0; // The total time in ms that the pin has been high (since reset)
this.starting = true;
+ this.debouncing = false; // True after a change of state while waiting for the 7ms debounce time to elapse
// Define 'node' to allow us to access 'this' from within callbacks (the 'var' is essential -
// otherwise there is only one global 'node' for all instances of DiscreteInputNode!)
var node = this;
- // This function is called whenever the input pin changes state. We update the currentState
- // and the ActiveTime variables, and send a message on the first output with the new state
- // Note: this function gets called spuriously when the interrupt is first enabled: in this
- // case x.value is undefined - we must test for this
+ // This function is called by the input pin change-of-state interrupt. If
+ // debounce is disabled, send the output message. Otherwise, if we are
+ // currently debouncing, ignore this interrupt. If we are not debouncing,
+ // schedule a re-read of the input pin in 7ms time, and set the debouncing flag
+ // Note: this function gets called spuriously when the interrupt is first enabled:
+ // in this case x.value is undefined - we must test for this
var interruptCallback = function (x) {
- if (x.value != undefined && node.currentState !== Number(x.value)) {
- node.currentState = Number(x.value);
- var now = Date.now();
- if (node.currentState === node.activeState) {
- node.lastActiveTime = now;
- } else if (!isNaN(node.lastActiveTime)) {
- node.totalActiveTime += now - node.lastActiveTime;
+ if (x.value !== undefined && node.currentState !== Number(x.value)) {
+ if (node.debounce) {
+ if (node.debouncing === false) {
+ node.debouncing = true;
+ setTimeout(function () { bonescript.digitalRead(node.pin, debounceCallback); }, 7);
+ }
+ } else {
+ sendStateMessage(x);
}
- var msg = {};
- msg.topic = node.topic;
- msg.payload = node.currentState;
- node.send([msg, null]);
}
};
+ // This function is called approx 7ms after a potential change-of-state which we
+ // are debouncing. Terminate the debounce, and send a message if the state has
+ // actually changed
+ var debounceCallback = function (x) {
+ node.debouncing = false;
+ if (x.value !== undefined && node.currentState !== Number(x.value)) {
+ sendStateMessage(x);
+ }
+ };
+
+ // This function is called when either the interruptCallback or the debounceCallback
+ // have determined we have a 'genuine' change of state. Update the currentState and
+ // ActiveTime variables, and send a message on the first output with the new state
+ var sendStateMessage = function (x) {
+ node.currentState = Number(x.value);
+ var now = Date.now();
+ // switch to process.hrtime()
+ if (node.currentState === node.activeState) {
+ node.lastActiveTime = now;
+ } else if (!isNaN(node.lastActiveTime)) {
+ node.totalActiveTime += now - node.lastActiveTime;
+ }
+ var msg = {};
+ msg.topic = node.topic;
+ msg.payload = node.currentState;
+ node.send([msg, null]);
+ };
+
// This function is called by the timer. It updates the ActiveTime variables, and sends a
// message on the second output with the latest value of the total active time, in seconds
var timerCallback = function () {
@@ -75,21 +105,31 @@ function DiscreteInputNode(n) {
var now = Date.now();
node.totalActiveTime += now - node.lastActiveTime;
node.lastActiveTime = now;
+ // switch to process.hrtime()
}
var msg = {};
msg.topic = node.topic;
msg.payload = node.totalActiveTime / 1000;
node.send([null, msg]);
- // Re-synchronise the pin state if we have missed a state change interrupt for some reason
- bonescript.digitalRead(node.pin, interruptCallback);
+ // Re-synchronise the pin state if we have missed a state change interrupt for some
+ // reason, and we are not in the process of debouncing one
+ if (node.debouncing === false) {
+ bonescript.digitalRead(node.pin, interruptCallback);
+ }
};
- // This function is called when we receive an input message. Clear the ActiveTime variables
- // (so we start counting from zero again)
- var inputCallback = function (msg) {
- node.totalActiveTime = 0;
+ // This function is called when we receive an input message. If the topic is "load"
+ // set the totalActiveTime to the numeric value of the payload, if possible. Otherwise
+ // clear the totalActiveTime (so we start counting from zero again)
+ var inputCallback = function (ipMsg) {
+ if (String(ipMsg.topic).search("load") < 0 || isFinite(ipMsg.payload) == false) {
+ node.totalActiveTime = 0;
+ } else {
+ node.totalActiveTime = Number(ipMsg.payload);
+ }
if (node.currentState === node.activeState) {
node.lastActiveTime = Date.now();
+ // switch to process.hrtime()
}
if (node.starting) {
node.starting = false;
@@ -113,6 +153,7 @@ function DiscreteInputNode(n) {
node.currentState = Number(x.value);
if (node.currentState === node.activeState) {
node.lastActiveTime = Date.now();
+ // switch to process.hrtime()
}
// Attempt to attach a change-of-state interrupt handler to the pin. If we succeed,
// set the input event and interval handlers, then send an initial message with the
@@ -132,8 +173,89 @@ function DiscreteInputNode(n) {
}
}
+// Node constructor for pulse-in
+// The node constructor
+function PulseInputNode(n) {
+ RED.nodes.createNode(this, n);
+
+ // Store local copies of the node configuration (as defined in the .html)
+ this.topic = n.topic; // the topic is not currently used
+ this.pin = n.pin; // The Beaglebone Black pin identifying string
+ this.updateInterval = n.updateInterval * 1000; // How often to send totalActiveTime messages
+ this.countType = n.countType;
+ this.countUnit = n.countUnit;
+ this.countRate = n.countRate;
+
+ this.interruptAttached = false; // Flag: should we detach interrupt when we are closed?
+ this.intervalId = null; // Remember the timer ID so we can delete it when we are closed
+
+ this.pulseTime = [[NaN, NaN], [NaN, NaN]];
+ this.pulseCount = 0;
+
+ // Define 'node' to allow us to access 'this' from within callbacks (the 'var' is essential -
+ // otherwise there is only one global 'node' for all instances of DiscreteInputNode!)
+ var node = this;
+
+ var interruptCallback = function (x) {
+ node.pulseTime = node.pulseTime[[1], process.hrtime()];
+ node.pulseCount = node.pulseCount + 1;
+ };
+
+ var inputCallback = function (msg) {
+ if (String(msg.topic).search("load") < 0 || isFinite(msg.payload) == false) {
+ node.pulseCount = 0;
+ } else {
+ node.pulseCount = Number(msg.payload);
+ }
+ };
+
+ var timerCallback = function () {
+ var now = process.hrtime();
+ var lastTime = pulseTime[1][0] - pulseTime[0][0] + (pulseTime[1][1] - pulseTime[0][1]) / 1e9;
+ var thisTime = now[0] - pulseTime[1][0] + (now[1] - pulseTime[1][1]) / 1e9;
+ var msg = [{ topic:node.topic }, { topic:node.topic }];
+ msg[0].payload = node.countUnit * node.pulseCount;
+ msg[1].payload = node.countRate / Math.max(thisTime, lastTime);
+ node.send(msg);
+ };
+
+ // If we have a valid pin, set it as an input and read the (digital) state
+ if (["P8_7", "P8_8", "P8_9", "P8_10", "P8_11", "P8_12", "P8_13", "P8_14", "P8_15",
+ "P8_16", "P8_17", "P8_18", "P8_19", "P8_26", "P9_11", "P9_12", "P9_13", "P9_14",
+ "P9_15", "P9_16", "P9_17", "P9_18", "P9_21", "P9_22", "P9_23", "P9_24", "P9_26",
+ "P9_27", "P9_30", "P9_41", "P9_42"].indexOf(node.pin) >= 0) {
+ // Don't set up interrupts & intervals until after the close event handler has been installed
+ process.nextTick(function () {
+ bonescript.pinMode(node.pin, bonescript.INPUT);
+ bonescript.digitalRead(node.pin, function (x) {
+ // Initialise the currentState based on the value read
+ node.currentState = Number(x.value);
+ // Attempt to attach a change-of-state interrupt handler to the pin. If we succeed,
+ // set the input event and interval handlers, then send an initial message with the
+ // pin state on the first output
+ var interruptType;
+ if (node.countType == "pulse") {
+ interruptType = bonescript.FALLING ;
+ } else {
+ interruptType = bonescript.CHANGE;
+ }
+ if (bonescript.attachInterrupt(node.pin, true, interruptType, interruptCallback)) {
+ node.interruptAttached = true;
+ node.on("input", inputCallback);
+ node.intervalId = setInterval(timerCallback, node.updateInterval);
+ } else {
+ node.error("Failed to attach interrupt");
+ }
+ });
+ });
+ } else {
+ node.error("Unconfigured input pin");
+ }
+}
+
// Register the node by name. This must be called before overriding any of the Node functions.
RED.nodes.registerType("discrete-in", DiscreteInputNode);
+RED.nodes.registerType("pulse-in", PulseInputNode);
// On close, detach the interrupt (if we attached one) and clear the interval (if we set one)
DiscreteInputNode.prototype.close = function () {
@@ -144,3 +266,13 @@ DiscreteInputNode.prototype.close = function () {
clearInterval(this.intervalId);
}
};
+
+// On close, detach the interrupt (if we attached one) and clear the interval (if we set one)
+PulseInputNode.prototype.close = function () {
+ if (this.interruptAttached) {
+ bonescript.detachInterrupt(this.pin);
+ }
+ if (this.intervalId !== null) {
+ clearInterval(this.intervalId);
+ }
+};
From 42c91dd6a32fb513a24356f9c785a6dc7a58f3a9 Mon Sep 17 00:00:00 2001
From: Maxwell Hadley
Date: Wed, 26 Feb 2014 16:35:46 +0000
Subject: [PATCH 36/43] Fixed issues with pulse-in; minor bug fixes
Got the pulse-in node working properly, and tidied up a few E&O in the
other nodes
---
hardware/BBB/144-analog-in.html | 2 +-
hardware/BBB/145-digital-in.html | 49 +++++++++--------
hardware/BBB/145-digital-in.js | 90 +++++++++++++++++++-------------
3 files changed, 81 insertions(+), 60 deletions(-)
diff --git a/hardware/BBB/144-analog-in.html b/hardware/BBB/144-analog-in.html
index 24573e2f..1b7b668e 100644
--- a/hardware/BBB/144-analog-in.html
+++ b/hardware/BBB/144-analog-in.html
@@ -66,7 +66,7 @@ scaled analogue input or NaN if a read error occurs.
Simple linear scaling is defined by setting the output values required for input
values of 0 and 1. You can apply more complicated scaling, e.g. for sensor linearisation,
by defining breakpoints at intermediate input values, with the desired output for
-each. Intermediate values are linearly interpolated.
+each. Values between breakpoints are linearly interpolated.
To reduce the effect of noise, enable averaging. This will read the input pin
diff --git a/hardware/BBB/145-digital-in.html b/hardware/BBB/145-digital-in.html
index 8a5fc409..23aa28bd 100644
--- a/hardware/BBB/145-digital-in.html
+++ b/hardware/BBB/145-digital-in.html
@@ -79,16 +79,18 @@
@@ -98,11 +100,12 @@ of the active time, not the pin state sent on the first output
category: 'advanced-input', // the palette category
color:"#c6dbef",
defaults: { // defines the editable properties of the node
- name: { value:"" }, // along with default values.
- updateInterval: { value:60, required:true, validate:RED.validators.number() },
- topic: { value:"", required:true },
pin: { value:"", required:true },
- activeLow: { value:false, required:true}
+ activeLow: { value:false, required:true },
+ debounce: { value:false, required: true },
+ updateInterval: { value:60, required:true, validate:RED.validators.number() },
+ topic: { value:"" },
+ name: { value:"" }
},
inputs:1, // set the number of inputs - only 0 or 1
outputs:2, // set the number of outputs - 0 to n
@@ -161,15 +164,16 @@ of the active time, not the pin state sent on the first output
-
-
-
-
-
+
+
+
+
+
+
-
+
@@ -194,8 +198,9 @@ the total counts and the rate of counts/second, each with scaling applied.
Sends the total count message on the first output, and the current count
-rate message on the second output, at the chosen interval. Any input message
-will reset the total count
+rate message on the second output, at the chosen interval. An input message with topic 'load'
+and a numeric payload will set the total count to that value (no scaling is applied):
+any other input message will reset it to zero.
@@ -205,19 +210,19 @@ will reset the total count
category: 'advanced-input', // the palette category
color:"#c6dbef",
defaults: { // defines the editable properties of the node
- name: { value:"" }, // along with default values.
- updateInterval: { value:2, required:true, validate:RED.validators.number() },
- topic: { value:"" },
pin: { value:"", required:true },
countType: { value:"pulse", required:true },
countUnit: { value:1, required:true, validate:RED.validators.number() },
- countRate: { value:1, required:true, validate:RED.validators.number() }
+ countRate: { value:1, required:true, validate:RED.validators.number() },
+ updateInterval: { value:2, required:true, validate:RED.validators.number() },
+ topic: { value:"" },
+ name: { value:"" }
},
inputs:1, // set the number of inputs - only 0 or 1
outputs:2, // set the number of outputs - 0 to n
icon: "arrow-in.png", // set the icon (held in public/icons)
label: function() { // sets the default label contents
- return this.name || "discrete-in: " + this.pin;
+ return this.name || "pulse-in: " + this.pin;
},
labelStyle: function() { // sets the class to apply to the label
return this.name ? "node_label_italic" : "";
diff --git a/hardware/BBB/145-digital-in.js b/hardware/BBB/145-digital-in.js
index 2d928728..57a3dbc8 100644
--- a/hardware/BBB/145-digital-in.js
+++ b/hardware/BBB/145-digital-in.js
@@ -24,7 +24,7 @@ try {
require("util").log("[145-digital-in] Error: cannot find module 'bonescript'");
}
-// discrete-in node constructor
+// Node constructor for discrete-in
function DiscreteInputNode(n) {
RED.nodes.createNode(this, n);
@@ -38,6 +38,7 @@ function DiscreteInputNode(n) {
this.updateInterval = n.updateInterval * 1000; // How often to send totalActiveTime messages
this.debounce = n.debounce; // Enable switch contact debouncing algorithm
+ // Working variables
this.interruptAttached = false; // Flag: should we detach interrupt when we are closed?
this.intervalId = null; // Remember the timer ID so we can delete it when we are closed
this.currentState = 0; // The pin input state "1" or "0"
@@ -46,9 +47,9 @@ function DiscreteInputNode(n) {
this.totalActiveTime = 0; // The total time in ms that the pin has been high (since reset)
this.starting = true;
this.debouncing = false; // True after a change of state while waiting for the 7ms debounce time to elapse
+ this.debounceTimer = null;
- // Define 'node' to allow us to access 'this' from within callbacks (the 'var' is essential -
- // otherwise there is only one global 'node' for all instances of DiscreteInputNode!)
+ // Define 'node' to allow us to access 'this' from within callbacks
var node = this;
// This function is called by the input pin change-of-state interrupt. If
@@ -62,7 +63,7 @@ function DiscreteInputNode(n) {
if (node.debounce) {
if (node.debouncing === false) {
node.debouncing = true;
- setTimeout(function () { bonescript.digitalRead(node.pin, debounceCallback); }, 7);
+ node.debounceTimer = setTimeout(function () { bonescript.digitalRead(node.pin, debounceCallback); }, 7);
}
} else {
sendStateMessage(x);
@@ -70,10 +71,11 @@ function DiscreteInputNode(n) {
}
};
- // This function is called approx 7ms after a potential change-of-state which we
- // are debouncing. Terminate the debounce, and send a message if the state has
+ // This function is called approx 7ms after a potential change-of-state which is
+ // being debounced. Terminate the debounce, and send a message if the state has
// actually changed
var debounceCallback = function (x) {
+ node.debounceTimer = null;
node.debouncing = false;
if (x.value !== undefined && node.currentState !== Number(x.value)) {
sendStateMessage(x);
@@ -86,7 +88,6 @@ function DiscreteInputNode(n) {
var sendStateMessage = function (x) {
node.currentState = Number(x.value);
var now = Date.now();
- // switch to process.hrtime()
if (node.currentState === node.activeState) {
node.lastActiveTime = now;
} else if (!isNaN(node.lastActiveTime)) {
@@ -105,7 +106,6 @@ function DiscreteInputNode(n) {
var now = Date.now();
node.totalActiveTime += now - node.lastActiveTime;
node.lastActiveTime = now;
- // switch to process.hrtime()
}
var msg = {};
msg.topic = node.topic;
@@ -118,18 +118,18 @@ function DiscreteInputNode(n) {
}
};
- // This function is called when we receive an input message. If the topic is "load"
- // set the totalActiveTime to the numeric value of the payload, if possible. Otherwise
- // clear the totalActiveTime (so we start counting from zero again)
+ // This function is called when we receive an input message. If the topic contains
+ // 'load' (case insensitive) set the totalActiveTime to the numeric value of the
+ // payload, if possible. Otherwise clear the totalActiveTime (so we start counting
+ // from zero again)
var inputCallback = function (ipMsg) {
- if (String(ipMsg.topic).search("load") < 0 || isFinite(ipMsg.payload) == false) {
+ if (String(ipMsg.topic).search(/load/i) < 0 || isFinite(ipMsg.payload) == false) {
node.totalActiveTime = 0;
} else {
node.totalActiveTime = Number(ipMsg.payload);
}
if (node.currentState === node.activeState) {
node.lastActiveTime = Date.now();
- // switch to process.hrtime()
}
if (node.starting) {
node.starting = false;
@@ -146,6 +146,7 @@ function DiscreteInputNode(n) {
"P9_15", "P9_16", "P9_17", "P9_18", "P9_21", "P9_22", "P9_23", "P9_24", "P9_26",
"P9_27", "P9_30", "P9_41", "P9_42"].indexOf(node.pin) >= 0) {
// Don't set up interrupts & intervals until after the close event handler has been installed
+ bonescript.detachInterrupt(node.pin);
process.nextTick(function () {
bonescript.pinMode(node.pin, bonescript.INPUT);
bonescript.digitalRead(node.pin, function (x) {
@@ -174,48 +175,59 @@ function DiscreteInputNode(n) {
}
// Node constructor for pulse-in
-// The node constructor
function PulseInputNode(n) {
RED.nodes.createNode(this, n);
// Store local copies of the node configuration (as defined in the .html)
this.topic = n.topic; // the topic is not currently used
this.pin = n.pin; // The Beaglebone Black pin identifying string
- this.updateInterval = n.updateInterval * 1000; // How often to send totalActiveTime messages
- this.countType = n.countType;
- this.countUnit = n.countUnit;
- this.countRate = n.countRate;
+ this.updateInterval = n.updateInterval * 1000; // How often to send output messages
+ this.countType = n.countType; // Sets either 'edge' or 'pulse' counting
+ this.countUnit = n.countUnit; // Scaling appling to count output
+ this.countRate = n.countRate; // Scaling applied to rate output
- this.interruptAttached = false; // Flag: should we detach interrupt when we are closed?
- this.intervalId = null; // Remember the timer ID so we can delete it when we are closed
-
+ // Working variables
+ this.interruptAttached = false; // Flag: should we detach interrupt when we are closed?
+ this.intervalId = null; // Remember the timer ID so we can delete it when we are closed
+ this.pulseCount = 0; // (Unscaled) total pulse count
+ // Hold the hrtime of the last two pulses (with ns resolution)
this.pulseTime = [[NaN, NaN], [NaN, NaN]];
- this.pulseCount = 0;
- // Define 'node' to allow us to access 'this' from within callbacks (the 'var' is essential -
- // otherwise there is only one global 'node' for all instances of DiscreteInputNode!)
+ // Define 'node' to allow us to access 'this' from within callbacks
var node = this;
+ // Called by the edge or pulse interrupt. If this is a valid interrupt, record the
+ // pulse time and count the pulse
var interruptCallback = function (x) {
- node.pulseTime = node.pulseTime[[1], process.hrtime()];
- node.pulseCount = node.pulseCount + 1;
+ if (x.value !== undefined) {
+ node.pulseTime = [node.pulseTime[1], process.hrtime()];
+ node.pulseCount = node.pulseCount + 1;
+ }
};
+ // Called when an input message arrives. If the topic contains 'load' (case
+ // insensitive) and the payload is a valid number, set the count to that
+ // number, otherwise set it to zero
var inputCallback = function (msg) {
- if (String(msg.topic).search("load") < 0 || isFinite(msg.payload) == false) {
+ if (String(msg.topic).search(/load/i) < 0 || isFinite(msg.payload) == false) {
node.pulseCount = 0;
} else {
node.pulseCount = Number(msg.payload);
}
};
+ // Called by the message timer. Send two messages: the scaled pulse count on
+ // the first output and the scaled instantaneous pulse rate on the second.
+ // The instantaneous pulse rate is the reciprocal of the larger of either the
+ // time interval between the last two pulses, or the time interval since the last pulse.
var timerCallback = function () {
var now = process.hrtime();
- var lastTime = pulseTime[1][0] - pulseTime[0][0] + (pulseTime[1][1] - pulseTime[0][1]) / 1e9;
- var thisTime = now[0] - pulseTime[1][0] + (now[1] - pulseTime[1][1]) / 1e9;
+ var lastTime = node.pulseTime[1][0] - node.pulseTime[0][0] + (node.pulseTime[1][1] - node.pulseTime[0][1]) / 1e9;
+ var thisTime = now[0] - node.pulseTime[1][0] + (now[1] - node.pulseTime[1][1]) / 1e9;
var msg = [{ topic:node.topic }, { topic:node.topic }];
msg[0].payload = node.countUnit * node.pulseCount;
- msg[1].payload = node.countRate / Math.max(thisTime, lastTime);
+ // At startup, pulseTime contains NaN's: force the rate output to 0
+ msg[1].payload = node.countRate / Math.max(thisTime, lastTime) || 0;
node.send(msg);
};
@@ -225,17 +237,18 @@ function PulseInputNode(n) {
"P9_15", "P9_16", "P9_17", "P9_18", "P9_21", "P9_22", "P9_23", "P9_24", "P9_26",
"P9_27", "P9_30", "P9_41", "P9_42"].indexOf(node.pin) >= 0) {
// Don't set up interrupts & intervals until after the close event handler has been installed
+ bonescript.detachInterrupt(node.pin);
process.nextTick(function () {
bonescript.pinMode(node.pin, bonescript.INPUT);
bonescript.digitalRead(node.pin, function (x) {
// Initialise the currentState based on the value read
node.currentState = Number(x.value);
- // Attempt to attach a change-of-state interrupt handler to the pin. If we succeed,
- // set the input event and interval handlers, then send an initial message with the
- // pin state on the first output
+ // Attempt to attach an interrupt handler to the pin. If we succeed,
+ // set the input event and interval handlers
var interruptType;
- if (node.countType == "pulse") {
- interruptType = bonescript.FALLING ;
+ if (node.countType === "pulse") {
+ // interruptType = bonescript.FALLING; <- doesn't work in v0.2.4
+ interruptType = bonescript.RISING;
} else {
interruptType = bonescript.CHANGE;
}
@@ -253,11 +266,11 @@ function PulseInputNode(n) {
}
}
-// Register the node by name. This must be called before overriding any of the Node functions.
+// Register the nodes by name. This must be called before overriding any of the Node functions.
RED.nodes.registerType("discrete-in", DiscreteInputNode);
RED.nodes.registerType("pulse-in", PulseInputNode);
-// On close, detach the interrupt (if we attached one) and clear the interval (if we set one)
+// On close, detach the interrupt (if we attached one) and clear any active timers
DiscreteInputNode.prototype.close = function () {
if (this.interruptAttached) {
bonescript.detachInterrupt(this.pin);
@@ -265,6 +278,9 @@ DiscreteInputNode.prototype.close = function () {
if (this.intervalId !== null) {
clearInterval(this.intervalId);
}
+ if (this.debounceTimer !== null) {
+ clearTimeout(this.debounceTimer);
+ }
};
// On close, detach the interrupt (if we attached one) and clear the interval (if we set one)
From 430300fa04f0dbcc1a8615d6b4aa198a8c06e7db Mon Sep 17 00:00:00 2001
From: Maxwell Hadley
Date: Wed, 26 Feb 2014 18:37:11 +0000
Subject: [PATCH 37/43] Added discrete-out node
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The output node is currently in 145-digital-in. This situation is
hopefully only temporary…
---
hardware/BBB/145-digital-in.html | 128 ++++++++++++++++++++++++++++++-
hardware/BBB/145-digital-in.js | 92 +++++++++++++++++++---
2 files changed, 208 insertions(+), 12 deletions(-)
diff --git a/hardware/BBB/145-digital-in.html b/hardware/BBB/145-digital-in.html
index 23aa28bd..8f6e4e78 100644
--- a/hardware/BBB/145-digital-in.html
+++ b/hardware/BBB/145-digital-in.html
@@ -61,6 +61,14 @@
+
+
+
+
@@ -80,10 +88,10 @@
@@ -102,7 +115,8 @@ of the active time, not the pin state value sent on the first output.
defaults: { // defines the editable properties of the node
pin: { value:"", required:true },
activeLow: { value:false, required:true },
- debounce: { value:false, required: true },
+ debounce: { value:false, required:true },
+ outputOn: { value:"both", required:true },
updateInterval: { value:60, required:true, validate:RED.validators.number() },
topic: { value:"" },
name: { value:"" }
@@ -230,3 +244,111 @@ any other input message will reset it to zero.
});
+
+
+
+
+
+
+
+
diff --git a/hardware/BBB/145-digital-in.js b/hardware/BBB/145-digital-in.js
index 57a3dbc8..c401f494 100644
--- a/hardware/BBB/145-digital-in.js
+++ b/hardware/BBB/145-digital-in.js
@@ -37,6 +37,15 @@ function DiscreteInputNode(n) {
this.activeState = 1;
this.updateInterval = n.updateInterval * 1000; // How often to send totalActiveTime messages
this.debounce = n.debounce; // Enable switch contact debouncing algorithm
+ if (n.outputOn === "rising") {
+ this.activeEdges = [false, true];
+ } else if (n.outputOn === "falling") {
+ this.activeEdges = [true, false];
+ } else if (n.outputOn === "both") {
+ this.activeEdges = [true, true];
+ } else {
+ node.error("Invalid edge type: " + n.outputOn);
+ }
// Working variables
this.interruptAttached = false; // Flag: should we detach interrupt when we are closed?
@@ -93,10 +102,12 @@ function DiscreteInputNode(n) {
} else if (!isNaN(node.lastActiveTime)) {
node.totalActiveTime += now - node.lastActiveTime;
}
- var msg = {};
- msg.topic = node.topic;
- msg.payload = node.currentState;
- node.send([msg, null]);
+ if (node.activeEdges[node.currentState]) {
+ var msg = {};
+ msg.topic = node.topic;
+ msg.payload = node.currentState;
+ node.send([msg, null]);
+ }
};
// This function is called by the timer. It updates the ActiveTime variables, and sends a
@@ -131,10 +142,17 @@ function DiscreteInputNode(n) {
if (node.currentState === node.activeState) {
node.lastActiveTime = Date.now();
}
+ // On startup, send an initial activeTime message, but only send an
+ // initial currentState message if we are in both edges active mode
if (node.starting) {
node.starting = false;
- var msg = [{topic:node.topic}, {topic:node.topic}];
- msg[0].payload = node.currentState;
+ var msg;
+ if (node.activeEdges[0] && node.activeEdges[1]) {
+ msg = [{topic:node.topic}, {topic:node.topic}];
+ msg[0].payload = node.currentState;
+ } else {
+ msg = [null, {topic:node.topic}];
+ }
msg[1].payload = node.totalActiveTime;
node.send(msg);
}
@@ -239,8 +257,8 @@ function PulseInputNode(n) {
// Don't set up interrupts & intervals until after the close event handler has been installed
bonescript.detachInterrupt(node.pin);
process.nextTick(function () {
- bonescript.pinMode(node.pin, bonescript.INPUT);
- bonescript.digitalRead(node.pin, function (x) {
+ bonescript.pinMode(node.pin, bonescript.INPUT);
+ bonescript.digitalRead(node.pin, function (x) {
// Initialise the currentState based on the value read
node.currentState = Number(x.value);
// Attempt to attach an interrupt handler to the pin. If we succeed,
@@ -260,15 +278,71 @@ function PulseInputNode(n) {
node.error("Failed to attach interrupt");
}
});
- });
+ });
} else {
node.error("Unconfigured input pin");
}
}
+// Node constructor for discrete-out
+function DiscreteOutputNode(n) {
+ RED.nodes.createNode(this, n);
+
+ // Store local copies of the node configuration (as defined in the .html)
+ this.topic = n.topic; // the topic is not currently used
+ this.pin = n.pin; // The Beaglebone Black pin identifying string
+ this.defaultState = Number(n.defaultState); // What state to set up as
+ this.inverting = n.inverting;
+ this.toggle = n.toggle;
+
+ // Working variables
+ this.currentState = this.defaultState;
+
+ var node = this;
+
+ // If the input message paylod is numeric, values > 0.5 are 'true', otherwise use
+ // the truthiness of the payload. Apply the inversion flag before setting the output
+ var inputCallback = function (msg) {
+ var newState;
+ if (node.toggle) {
+ newState = node.currentState === 0 ? 1 : 0;
+ } else {
+ if (isFinite(Number(msg.payload))) {
+ newState = Number(msg.payload) > 0.5 ? true : false;
+ } else if (msg.payload) {
+ newState = true;
+ } else {
+ newState = false;
+ }
+ if (node.inverting) {
+ newState = !newState;
+ }
+ }
+ bonescript.digitalWrite(node.pin, newState ? 1 : 0);
+ node.currentState = newState;
+ };
+
+ // If we have a valid pin, set it as an output and set the default state
+ if (["P8_7", "P8_8", "P8_9", "P8_10", "P8_11", "P8_12", "P8_13", "P8_14", "P8_15",
+ "P8_16", "P8_17", "P8_18", "P8_19", "P8_26", "P9_11", "P9_12", "P9_13", "P9_14",
+ "P9_15", "P9_16", "P9_17", "P9_18", "P9_21", "P9_22", "P9_23", "P9_24", "P9_26",
+ "P9_27", "P9_30", "P9_41", "P9_42", "USR0", "USR1", "USR2", "USR3"].indexOf(node.pin) >= 0) {
+ // Don't set up interrupts & intervals until after the close event handler has been installed
+ bonescript.detachInterrupt(node.pin);
+ process.nextTick(function () {
+ bonescript.pinMode(node.pin, bonescript.OUTPUT);
+ node.on("input", inputCallback);
+ setTimeout(function () { bonescript.digitalWrite(node.pin, node.defaultState); }, 50);
+ });
+ } else {
+ node.error("Unconfigured output pin");
+ }
+}
+
// Register the nodes by name. This must be called before overriding any of the Node functions.
RED.nodes.registerType("discrete-in", DiscreteInputNode);
RED.nodes.registerType("pulse-in", PulseInputNode);
+RED.nodes.registerType("discrete-out", DiscreteOutputNode);
// On close, detach the interrupt (if we attached one) and clear any active timers
DiscreteInputNode.prototype.close = function () {
From e8df573ea3a5cc633412d8ac624a1749b7e365c0 Mon Sep 17 00:00:00 2001
From: Maxwell Hadley
Date: Wed, 26 Feb 2014 21:18:42 +0000
Subject: [PATCH 38/43] Added message output to discrete-out
---
hardware/BBB/145-digital-in.html | 2 +-
hardware/BBB/145-digital-in.js | 1 +
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/hardware/BBB/145-digital-in.html b/hardware/BBB/145-digital-in.html
index 8f6e4e78..30417071 100644
--- a/hardware/BBB/145-digital-in.html
+++ b/hardware/BBB/145-digital-in.html
@@ -342,7 +342,7 @@ the Inverting property is not applied to this value.
name: { value:"" }
},
inputs:1, // set the number of inputs - only 0 or 1
- outputs:0, // set the number of outputs - 0 to n
+ outputs:1, // set the number of outputs - 0 to n
icon: "arrow-out.png", // set the icon (held in public/icons)
label: function() { // sets the default label contents
return this.name || "discrete-out: " + this.pin;
diff --git a/hardware/BBB/145-digital-in.js b/hardware/BBB/145-digital-in.js
index c401f494..64ed6bfc 100644
--- a/hardware/BBB/145-digital-in.js
+++ b/hardware/BBB/145-digital-in.js
@@ -319,6 +319,7 @@ function DiscreteOutputNode(n) {
}
}
bonescript.digitalWrite(node.pin, newState ? 1 : 0);
+ node.send({ topic:node.topic, payload:newState });
node.currentState = newState;
};
From 65a0c9336ff55b6de70387b272a3afc681aa0831 Mon Sep 17 00:00:00 2001
From: Maxwell Hadley
Date: Thu, 27 Feb 2014 18:33:14 +0000
Subject: [PATCH 39/43] Added pulse-out node
---
hardware/BBB/145-digital-in.html | 129 +++++++++++++++++++++++++++++--
hardware/BBB/145-digital-in.js | 82 ++++++++++++++++++++
2 files changed, 203 insertions(+), 8 deletions(-)
diff --git a/hardware/BBB/145-digital-in.html b/hardware/BBB/145-digital-in.html
index 30417071..b23471df 100644
--- a/hardware/BBB/145-digital-in.html
+++ b/hardware/BBB/145-digital-in.html
@@ -91,7 +91,7 @@ Discrete input for the Beaglebone Black. Sends a message with payload 0 or 1 on
first output when the pin changes state, and logs the total time in the active state.
-The node sends a message with a payload of the current total active time
+Sends a message with a payload of the current total active time
(in seconds) on the second output at selectable intervals. An input message with topic 'load'
and a numeric payload will set the total active time to that value: any other input message
will reset it to zero.
@@ -207,8 +207,8 @@ press. When using buttons or switches, enable debouncing to improve reliability.
+
+
+
+
+
+
+
+
+
+
diff --git a/hardware/BBB/145-digital-in.js b/hardware/BBB/145-digital-in.js
index 64ed6bfc..a75df955 100644
--- a/hardware/BBB/145-digital-in.js
+++ b/hardware/BBB/145-digital-in.js
@@ -340,10 +340,85 @@ function DiscreteOutputNode(n) {
}
}
+// Node constructor for pulse-out
+function PulseOutputNode(n) {
+ RED.nodes.createNode(this, n);
+
+ // Store local copies of the node configuration (as defined in the .html)
+ this.topic = n.topic; // the topic is not currently used
+ this.pin = n.pin; // The Beaglebone Black pin identifying string
+ this.pulseState = Number(n.pulseState); // What state the pulse will be..
+ this.defaultState = this.pulseState === 1 ? 0 : 1;
+ this.retriggerable = n.retriggerable;
+ this.pulseTime = n.pulseTime * 1000; // Pulse width in milliseconds
+
+ // Working variables
+ this.pulseTimer = null; // Non-null while a pulse is being generated
+
+ var node = this;
+
+ // Generate a pulse in response to an input message. If the topic includes the text
+ // 'time' (case insensitive) and the payload is numeric, use this value as the
+ // pulse time. Otherwise use the value from the properties dialog.
+ // If the resulting pulse time is < 1ms, do nothing.
+ // If the pulse mode is not retriggerable, then if no pulseTimer is active, generate
+ // a pulse. If the pulse mode is retriggerable, and a pulseTimer is active, cancel it.
+ // If no timer is active, set the pulse output. In both cases schedule a new pulse
+ // timer.
+ var inputCallback = function (msg) {
+ var time = node.pulseTime;
+ if (String(msg.topic).search(/time/i) >= 0 && isFinite(msg.payload)) {
+ time = msg.payload * 1000;
+ }
+ if (time >= 1) {
+ if (node.retriggerable === false) {
+ if (node.pulseTimer === null) {
+ node.pulseTimer = setTimeout(endPulseCallback, time);
+ bonescript.digitalWrite(node.pin, node.pulseState);
+ node.send({ topic:node.topic, payload:node.pulseState });
+ }
+ } else {
+ if (node.pulseTimer !== null) {
+ clearTimeout(node.pulseTimer);
+ } else {
+ bonescript.digitalWrite(node.pin, node.pulseState);
+ node.send({ topic:node.topic, payload:node.pulseState });
+ }
+ node.pulseTimer = setTimeout(endPulseCallback, time);
+ }
+ }
+ };
+
+ // At the end of the pulse, restore the default state and set the timer to null
+ var endPulseCallback = function () {
+ node.pulseTimer = null;
+ bonescript.digitalWrite(node.pin, node.defaultState);
+ node.send({ topic:node.topic, payload:node.defaultState });
+ };
+
+ // If we have a valid pin, set it as an output and set the default state
+ if (["P8_7", "P8_8", "P8_9", "P8_10", "P8_11", "P8_12", "P8_13", "P8_14", "P8_15",
+ "P8_16", "P8_17", "P8_18", "P8_19", "P8_26", "P9_11", "P9_12", "P9_13", "P9_14",
+ "P9_15", "P9_16", "P9_17", "P9_18", "P9_21", "P9_22", "P9_23", "P9_24", "P9_26",
+ "P9_27", "P9_30", "P9_41", "P9_42", "USR0", "USR1", "USR2", "USR3"].indexOf(node.pin) >= 0) {
+ // Don't set up interrupts & intervals until after the close event handler has been installed
+ bonescript.detachInterrupt(node.pin);
+ process.nextTick(function () {
+ bonescript.pinMode(node.pin, bonescript.OUTPUT);
+ node.on("input", inputCallback);
+ // Set the pin to the default stte once the dust settles
+ setTimeout(endPulseCallback, 50);
+ });
+ } else {
+ node.error("Unconfigured output pin");
+ }
+}
+
// Register the nodes by name. This must be called before overriding any of the Node functions.
RED.nodes.registerType("discrete-in", DiscreteInputNode);
RED.nodes.registerType("pulse-in", PulseInputNode);
RED.nodes.registerType("discrete-out", DiscreteOutputNode);
+RED.nodes.registerType("pulse-out", PulseOutputNode);
// On close, detach the interrupt (if we attached one) and clear any active timers
DiscreteInputNode.prototype.close = function () {
@@ -367,3 +442,10 @@ PulseInputNode.prototype.close = function () {
clearInterval(this.intervalId);
}
};
+
+// On close, clear an active pulse timer
+PulseOutputNode.prototype.close = function () {
+ if (this.pulseTimer !== null) {
+ clearTimeout(this.pulseTimer);
+ }
+};
From f0ea85570fb9b2d24019f1bdac02b367c9518292 Mon Sep 17 00:00:00 2001
From: Maxwell Hadley
Date: Thu, 27 Feb 2014 19:53:56 +0000
Subject: [PATCH 40/43] Changed comments to match the format used in
145-digital-in.*
---
hardware/BBB/144-analog-in.html | 9 +++++----
hardware/BBB/144-analog-in.js | 5 ++---
2 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/hardware/BBB/144-analog-in.html b/hardware/BBB/144-analog-in.html
index 1b7b668e..4b47219b 100644
--- a/hardware/BBB/144-analog-in.html
+++ b/hardware/BBB/144-analog-in.html
@@ -14,7 +14,7 @@
limitations under the License.
-->
-
+
-
+
-
+
-
-
-
-
-
-
diff --git a/hardware/BBB/144-analog-in.js b/hardware/BBB/144-analog-in.js
deleted file mode 100644
index 5c3ac8b7..00000000
--- a/hardware/BBB/144-analog-in.js
+++ /dev/null
@@ -1,84 +0,0 @@
-/**
- * Copyright 2014 Maxwell R Hadley
- *
- * 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.
- **/
-
-// Require main module
-var RED = require(process.env.NODE_RED_HOME+"/red/red");
-
-// Require bonescript
-try {
- var bonescript = require("bonescript");
-} catch (err) {
- require("util").log("[144-analog-in] Error: cannot find module 'bonescript'");
-}
-
-// Node constructor for analogue-in
-function AnalogInputNode(n) {
- // Create a RED node
- RED.nodes.createNode(this, n);
-
- // Store local copies of the node configuration (as defined in the .html)
- this.topic = n.topic;
- this.pin = n.pin;
- this.breakpoints = n.breakpoints;
- this.averaging = n.averaging;
- if (this.averaging) {
- this.averages = 10;
- } else {
- this.averages = 1;
- }
-
- // Define 'node' to allow us to access 'this' from within callbacks
- var node = this;
-
- // Variables used for input averaging
- var sum; // accumulates the input readings to be averaged
- var count; // keep track of the number of measurements made
-
- // The callback function for analogRead. Accumulates the required number of
- // measurements, then divides the total number, applies output scaling and
- // sends the result
- var analogReadCallback = function (x) {
- sum = sum + x.value;
- count = count - 1;
- if (count > 0) {
- bonescript.analogRead(node.pin, analogReadCallback);
- } else {
- var msg = {};
- msg.topic = node.topic;
- sum = sum/node.averages;
- // i is the index of the first breakpoint where the 'input' value is strictly
- // greater than the measurement (note: a measurement can never be == 1)
- var i = node.breakpoints.map(function (breakpoint) { return sum >= breakpoint.input; }).indexOf(false);
- msg.payload = node.breakpoints[i-1].output + (node.breakpoints[i].output - node.breakpoints[i-1].output) *
- (sum - node.breakpoints[i-1].input)/(node.breakpoints[i].input - node.breakpoints[i-1].input);
- node.send(msg);
- }
- };
-
- // If we have a valid pin, set the input event handler to Bonescript's analogRead
- if (["P9_39", "P9_40", "P9_37", "P9_38", "P9_33", "P9_36", "P9_35"].indexOf(node.pin) >= 0) {
- node.on("input", function (msg) {
- sum = 0;
- count = node.averages;
- bonescript.analogRead(node.pin, analogReadCallback);
- });
- } else {
- node.error("Unconfigured input pin");
- }
-}
-
-// Register the node by name. This must be called before overriding any of the Node functions.
-RED.nodes.registerType("analog-in", AnalogInputNode);
diff --git a/hardware/BBB/145-digital-in.html b/hardware/BBB/145-BBB-hardware.html
similarity index 69%
rename from hardware/BBB/145-digital-in.html
rename to hardware/BBB/145-BBB-hardware.html
index b23471df..3f5dea87 100644
--- a/hardware/BBB/145-digital-in.html
+++ b/hardware/BBB/145-BBB-hardware.html
@@ -14,6 +14,175 @@
limitations under the License.
-->
+
+
+
+
+
+
+
+
+
-
+
-
+
-
+
-
+