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:
parent
955fd1ad46
commit
f46b59d69f
@ -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
|
||||
</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);
|
||||
});
|
||||
|
@ -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");
|
||||
}
|
||||
|
@ -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");
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user