1
0
mirror of https://github.com/node-red/node-red-nodes.git synced 2023-10-10 13:36:58 +02: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:
Maxwell Hadley 2014-02-12 22:27:08 +00:00
parent 955fd1ad46
commit f46b59d69f
3 changed files with 65 additions and 53 deletions

View File

@ -37,11 +37,16 @@
Input Scaling
</div>
<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>
</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>
<span style="float:right; margin-top:4px">
<input type="checkbox" id="node-input-averaging" style="display:inline-block; width:auto; vertical-align:top;"> Averaging&nbsp;
</span>
</div>
<div class="form-row">
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
@ -52,17 +57,21 @@
<!-- Add help text -->
<script type="text/x-red" data-help-name="analog-in">
<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>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>
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
each. Intermediate values are linearly interpolated.
</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>
<!-- Register the node -->
@ -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 = $('<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 breakpointField = $('<span/>').appendTo(row);
var inputValueField = $('<input/>',
@ -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);
});

View File

@ -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");
}

View File

@ -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");
}