1
0
mirror of https://github.com/node-red/node-red-nodes.git synced 2023-10-10 13:36:58 +02:00

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
This commit is contained in:
Maxwell Hadley 2014-02-26 16:35:46 +00:00
parent 6fe7caed5f
commit 42c91dd6a3
3 changed files with 81 additions and 60 deletions

View File

@ -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 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, 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. Values between breakpoints are linearly interpolated.
</p> </p>
<p> <p>
To reduce the effect of noise, enable averaging. This will read the input pin To reduce the effect of noise, enable averaging. This will read the input pin

View File

@ -79,16 +79,18 @@
<!-- Help text for discrete-in --> <!-- Help text for discrete-in -->
<script type="text/x-red" data-help-name="discrete-in"> <script type="text/x-red" data-help-name="discrete-in">
<p> <p>
Discrete input for the Beaglebone Black. Sends a message on the first output Discrete input for the Beaglebone Black. Sends a message with payload 0 or 1 on the
each time the pin changes state, and records the total time in the active state first output each time the pin changes state, and logs the total time in the active state.
</p> </p>
<p> <p>
A timer updates the total active time, sending a message on the second output The node periodically sends a message with a payload of the current total active time
at the chosen update interval. Any input message will reset the 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.
</p> </p>
<p> <p>
The active state may be set to be high or low: this only affects the calculation The active state may be set to be high or low: this only affects the calculation
of the active time, not the pin state sent on the first output of the active time, not the pin state value sent on the first output.
</p> </p>
</script> </script>
@ -98,11 +100,12 @@ of the active time, not the pin state sent on the first output
category: 'advanced-input', // the palette category category: 'advanced-input', // the palette category
color:"#c6dbef", color:"#c6dbef",
defaults: { // defines the editable properties of the node 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 }, 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 inputs:1, // set the number of inputs - only 0 or 1
outputs:2, // set the number of outputs - 0 to n 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
<option value="pulse">pulses</option> <option value="pulse">pulses</option>
<option value="edge">edges</option> <option value="edge">edges</option>
</select> </select>
<div class="form-row">
<label for="node-input-countUnit"><i class="icon-repeat"></i> Scaling</label>
<input id="node-input-countUnit" type="text" style="width: 65px">
<label> units per count</label>
</div> </div>
<div class="form-row"> <div class="form-row">
<label for="node-input-countRate"><i class="icon-repeat"></i> Rate</label> <label for="node-input-countUnit">Scaling</label>
<input id="node-input-countUnit" type="text" style="width: 65px">
<label style="width: 60%"> units per count</label>
</div>
<div class="form-row">
<label for="node-input-countRate">Rate</label>
<input id="node-input-countRate" type="text" style="width: 65px"> <input id="node-input-countRate" type="text" style="width: 65px">
<label> units per count/second</label> <label style="width: 60%"> units per count/second</label>
</div> </div>
<div class="form-row"> <div class="form-row">
<label for="node-input-updateInterval"><i class="icon-repeat"></i> Update at</label> <label for="node-input-updateInterval"><i class="icon-repeat"></i> Update at</label>
@ -194,8 +198,9 @@ the total counts and the rate of counts/second, each with scaling applied.
</p> </p>
<p> <p>
Sends the total count message on the first output, and the current count 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 rate message on the second output, at the chosen interval. An input message with topic 'load'
will reset the total count 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.
</p> </p>
</script> </script>
@ -205,19 +210,19 @@ will reset the total count
category: 'advanced-input', // the palette category category: 'advanced-input', // the palette category
color:"#c6dbef", color:"#c6dbef",
defaults: { // defines the editable properties of the node 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 }, pin: { value:"", required:true },
countType: { value:"pulse", required:true }, countType: { value:"pulse", required:true },
countUnit: { value:1, required:true, validate:RED.validators.number() }, 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 inputs:1, // set the number of inputs - only 0 or 1
outputs:2, // set the number of outputs - 0 to n outputs:2, // set the number of outputs - 0 to n
icon: "arrow-in.png", // set the icon (held in public/icons) icon: "arrow-in.png", // set the icon (held in public/icons)
label: function() { // sets the default label contents 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 labelStyle: function() { // sets the class to apply to the label
return this.name ? "node_label_italic" : ""; return this.name ? "node_label_italic" : "";

View File

@ -24,7 +24,7 @@ try {
require("util").log("[145-digital-in] Error: cannot find module 'bonescript'"); require("util").log("[145-digital-in] Error: cannot find module 'bonescript'");
} }
// discrete-in node constructor // Node constructor for discrete-in
function DiscreteInputNode(n) { function DiscreteInputNode(n) {
RED.nodes.createNode(this, 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.updateInterval = n.updateInterval * 1000; // How often to send totalActiveTime messages
this.debounce = n.debounce; // Enable switch contact debouncing algorithm this.debounce = n.debounce; // Enable switch contact debouncing algorithm
// Working variables
this.interruptAttached = false; // Flag: should we detach interrupt when we are closed? 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.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.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.totalActiveTime = 0; // The total time in ms that the pin has been high (since reset)
this.starting = true; this.starting = true;
this.debouncing = false; // True after a change of state while waiting for the 7ms debounce time to elapse 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 - // Define 'node' to allow us to access 'this' from within callbacks
// otherwise there is only one global 'node' for all instances of DiscreteInputNode!)
var node = this; var node = this;
// This function is called by the input pin change-of-state interrupt. If // 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.debounce) {
if (node.debouncing === false) { if (node.debouncing === false) {
node.debouncing = true; node.debouncing = true;
setTimeout(function () { bonescript.digitalRead(node.pin, debounceCallback); }, 7); node.debounceTimer = setTimeout(function () { bonescript.digitalRead(node.pin, debounceCallback); }, 7);
} }
} else { } else {
sendStateMessage(x); sendStateMessage(x);
@ -70,10 +71,11 @@ function DiscreteInputNode(n) {
} }
}; };
// This function is called approx 7ms after a potential change-of-state which we // This function is called approx 7ms after a potential change-of-state which is
// are debouncing. Terminate the debounce, and send a message if the state has // being debounced. Terminate the debounce, and send a message if the state has
// actually changed // actually changed
var debounceCallback = function (x) { var debounceCallback = function (x) {
node.debounceTimer = null;
node.debouncing = false; node.debouncing = false;
if (x.value !== undefined && node.currentState !== Number(x.value)) { if (x.value !== undefined && node.currentState !== Number(x.value)) {
sendStateMessage(x); sendStateMessage(x);
@ -86,7 +88,6 @@ function DiscreteInputNode(n) {
var sendStateMessage = function (x) { var sendStateMessage = function (x) {
node.currentState = Number(x.value); node.currentState = Number(x.value);
var now = Date.now(); var now = Date.now();
// switch to process.hrtime()
if (node.currentState === node.activeState) { if (node.currentState === node.activeState) {
node.lastActiveTime = now; node.lastActiveTime = now;
} else if (!isNaN(node.lastActiveTime)) { } else if (!isNaN(node.lastActiveTime)) {
@ -105,7 +106,6 @@ function DiscreteInputNode(n) {
var now = Date.now(); var now = Date.now();
node.totalActiveTime += now - node.lastActiveTime; node.totalActiveTime += now - node.lastActiveTime;
node.lastActiveTime = now; node.lastActiveTime = now;
// switch to process.hrtime()
} }
var msg = {}; var msg = {};
msg.topic = node.topic; 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" // This function is called when we receive an input message. If the topic contains
// set the totalActiveTime to the numeric value of the payload, if possible. Otherwise // 'load' (case insensitive) set the totalActiveTime to the numeric value of the
// clear the totalActiveTime (so we start counting from zero again) // payload, if possible. Otherwise clear the totalActiveTime (so we start counting
// from zero again)
var inputCallback = function (ipMsg) { 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; node.totalActiveTime = 0;
} else { } else {
node.totalActiveTime = Number(ipMsg.payload); node.totalActiveTime = Number(ipMsg.payload);
} }
if (node.currentState === node.activeState) { if (node.currentState === node.activeState) {
node.lastActiveTime = Date.now(); node.lastActiveTime = Date.now();
// switch to process.hrtime()
} }
if (node.starting) { if (node.starting) {
node.starting = false; 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_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) {
// Don't set up interrupts & intervals until after the close event handler has been installed // Don't set up interrupts & intervals until after the close event handler has been installed
bonescript.detachInterrupt(node.pin);
process.nextTick(function () { 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) {
@ -174,48 +175,59 @@ function DiscreteInputNode(n) {
} }
// Node constructor for pulse-in // Node constructor for pulse-in
// The node constructor
function PulseInputNode(n) { function PulseInputNode(n) {
RED.nodes.createNode(this, n); RED.nodes.createNode(this, n);
// Store local copies of the node configuration (as defined in the .html) // Store local copies of the node configuration (as defined in the .html)
this.topic = n.topic; // the topic is not currently used this.topic = n.topic; // the topic is not currently used
this.pin = n.pin; // The Beaglebone Black pin identifying string this.pin = n.pin; // The Beaglebone Black pin identifying string
this.updateInterval = n.updateInterval * 1000; // How often to send totalActiveTime messages this.updateInterval = n.updateInterval * 1000; // How often to send output messages
this.countType = n.countType; this.countType = n.countType; // Sets either 'edge' or 'pulse' counting
this.countUnit = n.countUnit; this.countUnit = n.countUnit; // Scaling appling to count output
this.countRate = n.countRate; this.countRate = n.countRate; // Scaling applied to rate output
this.interruptAttached = false; // Flag: should we detach interrupt when we are closed? // Working variables
this.intervalId = null; // Remember the timer ID so we can delete it when we are closed 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.pulseTime = [[NaN, NaN], [NaN, NaN]];
this.pulseCount = 0;
// 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
// otherwise there is only one global 'node' for all instances of DiscreteInputNode!)
var node = this; 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) { var interruptCallback = function (x) {
node.pulseTime = node.pulseTime[[1], process.hrtime()]; if (x.value !== undefined) {
node.pulseCount = node.pulseCount + 1; 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) { 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; node.pulseCount = 0;
} else { } else {
node.pulseCount = Number(msg.payload); 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 timerCallback = function () {
var now = process.hrtime(); var now = process.hrtime();
var lastTime = pulseTime[1][0] - pulseTime[0][0] + (pulseTime[1][1] - pulseTime[0][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] - pulseTime[1][0] + (now[1] - pulseTime[1][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 }]; var msg = [{ topic:node.topic }, { topic:node.topic }];
msg[0].payload = node.countUnit * node.pulseCount; 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); 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_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) {
// Don't set up interrupts & intervals until after the close event handler has been installed // Don't set up interrupts & intervals until after the close event handler has been installed
bonescript.detachInterrupt(node.pin);
process.nextTick(function () { 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 based on the value read // Initialise the currentState based on the value read
node.currentState = Number(x.value); node.currentState = Number(x.value);
// Attempt to attach a change-of-state interrupt handler to the pin. If we succeed, // Attempt to attach an interrupt handler to the pin. If we succeed,
// set the input event and interval handlers, then send an initial message with the // set the input event and interval handlers
// pin state on the first output
var interruptType; var interruptType;
if (node.countType == "pulse") { if (node.countType === "pulse") {
interruptType = bonescript.FALLING ; // interruptType = bonescript.FALLING; <- doesn't work in v0.2.4
interruptType = bonescript.RISING;
} else { } else {
interruptType = bonescript.CHANGE; 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("discrete-in", DiscreteInputNode);
RED.nodes.registerType("pulse-in", PulseInputNode); 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 () { DiscreteInputNode.prototype.close = function () {
if (this.interruptAttached) { if (this.interruptAttached) {
bonescript.detachInterrupt(this.pin); bonescript.detachInterrupt(this.pin);
@ -265,6 +278,9 @@ DiscreteInputNode.prototype.close = function () {
if (this.intervalId !== null) { if (this.intervalId !== null) {
clearInterval(this.intervalId); 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) // On close, detach the interrupt (if we attached one) and clear the interval (if we set one)