mirror of
https://github.com/node-red/node-red-nodes.git
synced 2025-03-01 10:37:43 +00:00
Added analogue scaling & averaging; discrete check reads
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
This commit is contained in:
parent
955fd1ad46
commit
f46b59d69f
@ -37,11 +37,16 @@
|
|||||||
Input Scaling
|
Input Scaling
|
||||||
</div>
|
</div>
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<div id="node-input-breakpoint-container-div" style="border-radius: 5px; height: 310px; padding: 5px; border: 1px solid #ccc; overflow-y:scroll;">
|
<div id="node-input-breakpoint-container-div" style="border-radius: 5px; height: 132px; padding: 5px; border: 1px solid #ccc; overflow-y:scroll;">
|
||||||
<ol id="node-input-breakpoint-container" style=" list-style-type:none; margin: 0;">
|
<ol id="node-input-breakpoint-container" style=" list-style-type:none; margin: 0;">
|
||||||
</ol>
|
</ol>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
<a href="#" class="btn btn-mini" id="node-input-add-breakpoint" style="margin-top: 4px;"><i class="icon-plus"></i> Add Breakpoint</a>
|
<a href="#" class="btn btn-mini" id="node-input-add-breakpoint" style="margin-top: 4px;"><i class="icon-plus"></i> Add Breakpoint</a>
|
||||||
|
<span style="float:right; margin-top:4px">
|
||||||
|
<input type="checkbox" id="node-input-averaging" style="display:inline-block; width:auto; vertical-align:top;"> Averaging
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
|
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
|
||||||
@ -52,17 +57,21 @@
|
|||||||
<!-- Add help text -->
|
<!-- Add help text -->
|
||||||
<script type="text/x-red" data-help-name="analog-in">
|
<script type="text/x-red" data-help-name="analog-in">
|
||||||
<p>
|
<p>
|
||||||
Analogue input for the Beaglebone Black. Reads an anlogue pin when triggered
|
Analogue input for the Beaglebone Black. Reads an analogue pin when triggered
|
||||||
</p>
|
</p>
|
||||||
<p>The output message topic is the node topic: the output message value is the
|
<p>The output message topic is the node topic: the output message value is the
|
||||||
scaled analogue input or NaN if an read error occurs (errors are logged).
|
scaled analogue input or NaN if a read error occurs.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Simple linear scaling is defined by setting the output values required for input
|
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,
|
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
|
by defining breakpoints at intermediate input values, with the desired output for
|
||||||
each. Intermediate values are linearly interpolated.
|
each. Intermediate values are linearly interpolated.
|
||||||
</p>
|
</p>
|
||||||
|
<p>
|
||||||
|
To reduce the effect of noise, enable averaging. This will read the input pin
|
||||||
|
voltage ten times in rapid succession for each input message and output the mean value.
|
||||||
|
</p>
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- Register the node -->
|
<!-- Register the node -->
|
||||||
@ -74,7 +83,8 @@ each. Intermediate values are linearly interpolated.
|
|||||||
name: { value:"" }, // along with default values.
|
name: { value:"" }, // along with default values.
|
||||||
topic: { value:"", required:true },
|
topic: { value:"", required:true },
|
||||||
pin: { 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
|
inputs:1, // set the number of inputs - only 0 or 1
|
||||||
outputs:1, // set the number of outputs - 0 to n
|
outputs:1, // set the number of outputs - 0 to n
|
||||||
@ -87,7 +97,7 @@ each. Intermediate values are linearly interpolated.
|
|||||||
},
|
},
|
||||||
oneditprepare: function () {
|
oneditprepare: function () {
|
||||||
function generateBreakpoint(breakpoint, insert) {
|
function generateBreakpoint(breakpoint, insert) {
|
||||||
var container = $('<li/>', {style:"margin:0; padding:8px 0px; border-bottom: 1px solid #ccc;"});
|
var container = $('<li/>', {style:"margin:0; padding:4px; padding-left 10px;"});
|
||||||
var row = $('<div/>').appendTo(container);
|
var row = $('<div/>').appendTo(container);
|
||||||
var breakpointField = $('<span/>').appendTo(row);
|
var breakpointField = $('<span/>').appendTo(row);
|
||||||
var inputValueField = $('<input/>',
|
var inputValueField = $('<input/>',
|
||||||
@ -117,10 +127,10 @@ each. Intermediate values are linearly interpolated.
|
|||||||
if (insert === true) {
|
if (insert === true) {
|
||||||
var last = $("#node-input-breakpoint-container").children().last();
|
var last = $("#node-input-breakpoint-container").children().last();
|
||||||
var prev = last.prev();
|
var prev = last.prev();
|
||||||
inputValueField.val(((last.find(".node-input-breakpoint-input-value").val() - 0) +
|
inputValueField.val((Number(last.find(".node-input-breakpoint-input-value").val()) +
|
||||||
(prev.find(".node-input-breakpoint-input-value").val() - 0))/2);
|
Number(prev.find(".node-input-breakpoint-input-value").val()))/2);
|
||||||
outputValueField.val(((last.find(".node-input-breakpoint-output-value").val() - 0) +
|
outputValueField.val((Number(last.find(".node-input-breakpoint-output-value").val()) +
|
||||||
(prev.find(".node-input-breakpoint-output-value").val() - 0))/2);
|
Number(prev.find(".node-input-breakpoint-output-value").val()))/2);
|
||||||
last.before(container);
|
last.before(container);
|
||||||
} else {
|
} else {
|
||||||
inputValueField.val(breakpoint.input);
|
inputValueField.val(breakpoint.input);
|
||||||
@ -139,9 +149,9 @@ each. Intermediate values are linearly interpolated.
|
|||||||
generateBreakpoint(breakpoint, false);
|
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) {
|
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);
|
$("#dialog").on("dialogresize", switchDialogResize);
|
||||||
@ -162,8 +172,8 @@ each. Intermediate values are linearly interpolated.
|
|||||||
breakpoints.each(function (i) {
|
breakpoints.each(function (i) {
|
||||||
var breakpoint = $(this);
|
var breakpoint = $(this);
|
||||||
var r = {};
|
var r = {};
|
||||||
r.input = breakpoint.find(".node-input-breakpoint-input-value").val() - 0
|
r.input = Number(breakpoint.find(".node-input-breakpoint-input-value").val());
|
||||||
r.output = breakpoint.find(".node-input-breakpoint-output-value").val() - 0;
|
r.output = Number(breakpoint.find(".node-input-breakpoint-output-value").val());
|
||||||
r.mutable = breakpoint.find(".node-input-breakpoint-mutable").attr("mutable") == "true";
|
r.mutable = breakpoint.find(".node-input-breakpoint-mutable").attr("mutable") == "true";
|
||||||
node.breakpoints.push(r);
|
node.breakpoints.push(r);
|
||||||
});
|
});
|
||||||
|
@ -33,30 +33,49 @@ function AnalogInputNode(n) {
|
|||||||
this.topic = n.topic;
|
this.topic = n.topic;
|
||||||
this.pin = n.pin;
|
this.pin = n.pin;
|
||||||
this.breakpoints = n.breakpoints;
|
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 -
|
// 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!)
|
// otherwise there is only one global 'node' for all instances of AnalogInputNode!)
|
||||||
var node = this;
|
var node = this;
|
||||||
|
|
||||||
node.log("breakpoints:");
|
// Variables used for input averaging
|
||||||
for (var i = 0; i < node.breakpoints.length; i++) {
|
var sum; // accumulates the input readings to be averaged
|
||||||
node.log(i + ": {input:" + node.breakpoints[i].input + ", output:" + node.breakpoints[i].output + ", mutable:" + node.breakpoints[i].mutable +"}");
|
var count; // keep track of the number of measurements made
|
||||||
}
|
|
||||||
|
// The callback function for analogRead. Accumulates the required number of
|
||||||
// A callback function variable seems to be more reliable than a lambda ?!
|
// measurements, then divides the total number, applies output scaling and
|
||||||
var readCallback = function (x) {
|
// sends the result
|
||||||
var msg = {};
|
var analogReadCallback = function (x) {
|
||||||
msg.topic = node.topic;
|
sum = sum + x.value;
|
||||||
msg.payload = x.value;
|
count = count - 1;
|
||||||
if (isNaN(x.value)) {
|
if (count > 0) {
|
||||||
node.log(x.err);
|
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 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) {
|
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 {
|
} else {
|
||||||
node.error("Unconfigured input pin");
|
node.error("Unconfigured input pin");
|
||||||
}
|
}
|
||||||
|
@ -53,14 +53,8 @@ function DiscreteInputNode(n) {
|
|||||||
// Note: this function gets called spuriously when the interrupt is first enabled: in this
|
// Note: this function gets called spuriously when the interrupt is first enabled: in this
|
||||||
// case x.value is undefined - we must test for this
|
// case x.value is undefined - we must test for this
|
||||||
var interruptCallback = function (x) {
|
var interruptCallback = function (x) {
|
||||||
// node.log("interruptCallback: x.value = " + x.value);
|
if (x.value != undefined && node.currentState !== Number(x.value)) {
|
||||||
// node.log("interruptCallback: node.currentState = " + node.currentState);
|
node.currentState = Number(x.value);
|
||||||
// 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;
|
|
||||||
var now = Date.now();
|
var now = Date.now();
|
||||||
if (node.currentState === node.activeState) {
|
if (node.currentState === node.activeState) {
|
||||||
node.lastActiveTime = now;
|
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
|
// This function is called by the timer. It updates the ActiveTime variables, and sends a
|
||||||
// message on the second output with the latest value of the total active time, in seconds
|
// message on the second output with the latest value of the total active time, in seconds
|
||||||
var timerCallback = function () {
|
var timerCallback = function () {
|
||||||
// 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) {
|
if (node.currentState === node.activeState) {
|
||||||
var now = Date.now();
|
var now = Date.now();
|
||||||
node.totalActiveTime += now - node.lastActiveTime;
|
node.totalActiveTime += now - node.lastActiveTime;
|
||||||
@ -89,14 +80,13 @@ function DiscreteInputNode(n) {
|
|||||||
msg.topic = node.topic;
|
msg.topic = node.topic;
|
||||||
msg.payload = node.totalActiveTime / 1000;
|
msg.payload = node.totalActiveTime / 1000;
|
||||||
node.send([null, msg]);
|
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
|
// This function is called when we receive an input message. Clear the ActiveTime variables
|
||||||
// (so we start counting from zero again)
|
// (so we start counting from zero again)
|
||||||
var inputCallback = function (msg) {
|
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;
|
node.totalActiveTime = 0;
|
||||||
if (node.currentState === node.activeState) {
|
if (node.currentState === node.activeState) {
|
||||||
node.lastActiveTime = Date.now();
|
node.lastActiveTime = Date.now();
|
||||||
@ -107,9 +97,6 @@ function DiscreteInputNode(n) {
|
|||||||
msg[0].payload = node.currentState;
|
msg[0].payload = node.currentState;
|
||||||
msg[1].payload = node.totalActiveTime;
|
msg[1].payload = node.totalActiveTime;
|
||||||
node.send(msg);
|
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",
|
"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_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) {
|
"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.pinMode(node.pin, bonescript.INPUT);
|
||||||
bonescript.digitalRead(node.pin, function (x) {
|
bonescript.digitalRead(node.pin, function (x) {
|
||||||
// Initialise the currentState and lastActveTime variables based on the value read
|
// Initialise the currentState and lastActveTime variables based on the value read
|
||||||
// node.log("digitalRead: x.value = " + x.value);
|
node.currentState = Number(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) {
|
if (node.currentState === node.activeState) {
|
||||||
node.lastActiveTime = Date.now();
|
node.lastActiveTime = Date.now();
|
||||||
}
|
}
|
||||||
@ -143,7 +126,7 @@ function DiscreteInputNode(n) {
|
|||||||
}
|
}
|
||||||
setTimeout(function () { node.emit("input", {}); }, 50);
|
setTimeout(function () { node.emit("input", {}); }, 50);
|
||||||
});
|
});
|
||||||
}, 50);
|
});
|
||||||
} else {
|
} else {
|
||||||
node.error("Unconfigured input pin");
|
node.error("Unconfigured input pin");
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user