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

Added pulse-in; added debouncing

Added the pulse-in node code. Added simple debouncing logic to
discrete-in (note: this implementation may be badly affected if there
is a lot of very fast bouncing.
This commit is contained in:
Maxwell Hadley 2014-02-23 15:20:05 +00:00
parent f46b59d69f
commit b95567dfb4
2 changed files with 272 additions and 26 deletions

View File

@ -14,7 +14,7 @@
limitations under the License.
-->
<!-- Define the edit dialog -->
<!-- Edit dialog for discrete-in -->
<script type="text/x-red" data-template-name="discrete-in">
<div class="form-row">
<label for="node-input-pin"><i class="icon-asterisk"></i>Input pin</label>
@ -56,6 +56,11 @@
<div class="form-row">
<label for="node-input-activeLow">Active low</label>
<input type="checkbox" id="node-input-activeLow" style="display:inline-block; width:auto; vertical-align:top;">
</div>
<div class="form-row">
<label for="node-input-debounce">Debounce</label>
<input type="checkbox" id="node-input-debounce" style="display:inline-block; width:auto; vertical-align:top;">
</div>
<div class="form-row">
<label for="node-input-updateInterval"><i class="icon-repeat"></i> Update at</label>
<input id="node-input-updateInterval" type="text" style="width: 65px">
@ -71,8 +76,7 @@
</div>
</script>
<!-- Add help text -->
<!-- Help text for discrete-in -->
<script type="text/x-red" data-help-name="discrete-in">
<p>
Discrete input for the Beaglebone Black. Sends a message on the first output
@ -88,7 +92,7 @@ of the active time, not the pin state sent on the first output
</p>
</script>
<!-- Register the node -->
<!-- Register discrete-in -->
<script type="text/javascript">
RED.nodes.registerType('discrete-in', {
category: 'advanced-input', // the palette category
@ -111,3 +115,113 @@ of the active time, not the pin state sent on the first output
}
});
</script>
<!-- Edit dialog for pulse-in -->
<script type="text/x-red" data-template-name="pulse-in">
<div class="form-row">
<label for="node-input-pin"><i class="icon-asterisk"></i>Input pin</label>
<select type="text" id="node-input-pin" style="width: 200px;">
<option value="">select pin</option>
<option value="P8_7">GPIO2_2 (P8 pin 7)</option>
<option value="P8_8">GPIO2_3 (P8 pin 8)</option>
<option value="P8_9">GPIO2_5 (P8 pin 9)</option>
<option value="P8_10">GPIO2_4 (P8 pin 10)</option>
<option value="P8_11">GPIO1_13 (P8 pin 11)</option>
<option value="P8_12">GPIO1_12 (P8 pin 12)</option>
<option value="P8_13">GPIO0_23 (P8 pin 13)</option>
<option value="P8_14">GPIO0_26 (P8 pin 14)</option>
<option value="P8_15">GPIO1_15 (P8 pin 15)</option>
<option value="P8_16">GPIO1_14 (P8 pin 16)</option>
<option value="P8_17">GPIO0_27 (P8 pin 17)</option>
<option value="P8_18">GPIO2_1 (P8 pin 18)</option>
<option value="P8_19">GPIO0_22 (P8 pin 19)</option>
<option value="P8_26">GPIO1_29 (P8 pin 26)</option>
<option value="P9_11">GPIO0_30 (P9 pin 11)</option>
<option value="P9_12">GPIO1_28 (P9 pin 12)</option>
<option value="P9_13">GPIO0_31 (P9 pin 13)</option>
<option value="P9_14">GPIO1_18 (P9 pin 14)</option>
<option value="P9_15">GPIO1_16 (P9 pin 15)</option>
<option value="P9_16">GPIO1_19 (P9 pin 16)</option>
<option value="P9_17">GPIO0_5 (P9 pin 17)</option>
<option value="P9_18">GPIO0_4 (P9 pin 18)</option>
<option value="P9_21">GPIO0_3 (P9 pin 21)</option>
<option value="P9_22">GPIO0_2 (P9 pin 22)</option>
<option value="P9_23">GPIO1_17 (P9 pin 23)</option>
<option value="P9_24">GPIO0_15 (P9 pin 24)</option>
<option value="P9_26">GPIO0_14 (P9 pin 26)</option>
<option value="P9_27">GPIO3_19 (P9 pin 27)</option>
<option value="P9_30">GPIO3_16 (P9 pin 30)</option>
<option value="P9_41">GPIO0_20 (P9 pin 41)</option>
<option value="P9_42">GPIO0_7 (P9 pin 42)</option>
</select>
</div>
<div class="form-row">
<label for="node-input-countType">Count</label>
<select type="text" id="node-input-countType" style="width: 100px;">
<option value="pulse">pulses</option>
<option value="edge">edges</option>
</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 class="form-row">
<label for="node-input-countRate"><i class="icon-repeat"></i> Rate</label>
<input id="node-input-countRate" type="text" style="width: 65px">
<label> units per count/second</label>
</div>
<div class="form-row">
<label for="node-input-updateInterval"><i class="icon-repeat"></i> Update at</label>
<input id="node-input-updateInterval" type="text" style="width: 65px">
<label>sec intervals</label>
</div>
<div class="form-row">
<label for="node-input-topic"><i class="icon-tasks"></i> Topic</label>
<input type="text" id="node-input-topic" placeholder="Topic">
</div>
<div class="form-row">
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
</script>
<!-- Help text for pulse-in -->
<script type="text/x-red" data-help-name="pulse-in">
<p>
Pulse input for the Beaglebone Black. Counts input pulses or pulse edges, outputs
the total counts and the rate of counts/second, each with scaling applied.
</p>
<p>
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
</p>
</script>
<!-- Register pulse-in -->
<script type="text/javascript">
RED.nodes.registerType('pulse-in', {
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() }
},
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;
},
labelStyle: function() { // sets the class to apply to the label
return this.name ? "node_label_italic" : "";
}
});
</script>

View File

@ -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,26 +36,57 @@ 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)) {
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);
}
}
};
// 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)) {
@ -65,7 +96,6 @@ function DiscreteInputNode(n) {
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
@ -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
// 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) {
// 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);
}
};