Add hardware nodes for Beaglebone Black: analogue in; discrete in/out; pulse in/out

This commit is contained in:
Maxwell Hadley 2014-03-05 21:21:52 +00:00
parent c63583ae81
commit 6cb70d73f1
3 changed files with 1148 additions and 0 deletions

View File

@ -69,6 +69,8 @@ Copyright 2013 IBM Corp. under [the Apache 2.0 license](LICENSE).
**101-scanBLE** - Scans for a particular Bluetooth Low Energy (BLE) device.
**145-BBB-hardware** - A collection of analogue & digital input & output nodes for the Beaglebone Black
### IO
**26-rawserial** - Only really needed for Windows boxes without serialport npm module installed.

View File

@ -0,0 +1,638 @@
<!--
Copyright 2014 Maxwell R Hadley
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<!-- Edit dialog for analogue-in -->
<script type="text/x-red" data-template-name="analogue-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: 150px;">
<option value="">select pin</option>
<option value="P9_39">AIN0 (P9 pin 39)</option>
<option value="P9_40">AIN1 (P9 pin 40)</option>
<option value="P9_37">AIN2 (P9 pin 37)</option>
<option value="P9_38">AIN3 (P9 pin 38)</option>
<option value="P9_33">AIN4 (P9 pin 33)</option>
<option value="P9_36">AIN5 (P9 pin 36)</option>
<option value="P9_35">AIN6 (P9 pin 35)</option>
</select>
</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">
Input Scaling
</div>
<div class="form-row">
<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>
<input type="text" id="node-input-name" placeholder="Name">
</div>
</script>
<!-- Help text for analogue-in -->
<script type="text/x-red" data-help-name="analogue-in">
<p>
Analogue input for the Beaglebone Black. Reads an analogue pin when triggered by
a message.
</p>
<p>The output message topic is the node topic: the output message value is the
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, e.g. for sensor linearisation,
by defining breakpoints at intermediate input values, with the desired output for
each. Values between breakpoints 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 analogue-in node -->
<script type="text/javascript">
RED.nodes.registerType('analogue-in', {
category: 'advanced-input', // the palette category
color:"#de7224",
defaults: { // defines the editable properties of the node
pin: { value:"", required:true },
topic: { value:"" },
breakpoints: { value:[{input:0.0, output:0.0, mutable:false}, {input:1.0, output:1.0, mutable:false}] },
averaging: { value:false, required:true },
name: { value:"" }
},
inputs:1, // set the number of inputs - only 0 or 1
outputs:1, // set the number of outputs - 0 to n
icon: "BBB.png", // set the icon (held in public/icons)
label: function() { // sets the default label contents
return this.name || "analogue-in: " + this.pin;
},
labelStyle: function() { // sets the class to apply to the label
return this.name ? "node_label_italic" : "";
},
oneditprepare: function () {
function generateBreakpoint(breakpoint, insert) {
var container = $('<li/>', {style:"margin:0; padding:4px; padding-left 10px;"});
var row = $('<div/>').appendTo(container);
var breakpointField = $('<span/>').appendTo(row);
var inputValueField = $('<input/>',
{disabled:"", class:"node-input-breakpoint-input-value", type:"text", style:"margin-left:5px; margin-right:2px; width:36%;"}).appendTo(breakpointField);
if (breakpoint.mutable) {
inputValueField.removeAttr("disabled");
}
breakpointField.append(" => ");
var outputValueField = $('<input/>',
{class:"node-input-breakpoint-output-value", type:"text", style:"margin-left:0px; width:36%;"}).appendTo(breakpointField);
var finalSpan = $('<span/>', {style:"float:right; margin-top:3px; margin-right:10px;"}).appendTo(row);
var mutableFlag = $('<span/>', {class:"node-input-breakpoint-mutable", style:"display:hide;"}).appendTo(row);
if (breakpoint.mutable) {
mutableFlag.attr("mutable", "true");
var deleteButton = $('<a/>', {href:"#", class:"btn btn-mini", style:"margin-left: 5px;"}).appendTo(finalSpan);
$('<i/>', {class:"icon-remove"}).appendTo(deleteButton);
deleteButton.click(function() {
container.css({"background":"#fee"});
container.fadeOut(300, function() {
$(this).remove();
});
});
} else {
mutableFlag.attr("mutable", "false");
}
if (insert === true) {
var last = $("#node-input-breakpoint-container").children().last();
var prev = last.prev();
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);
outputValueField.val(breakpoint.output);
$("#node-input-breakpoint-container").append(container);
}
}
$("#node-input-add-breakpoint").click(function () {
generateBreakpoint({input:0, output:0, mutable:true}, true);
$("#node-input-breakpoint-container-div").scrollTop($("#node-input-breakpoint-container-div").get(0).scrollHeight);
});
for (var i = 0; i < this.breakpoints.length; i++) {
var breakpoint = this.breakpoints[i];
generateBreakpoint(breakpoint, false);
}
// 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 - 299) + "px");
};
$("#dialog").on("dialogresize", switchDialogResize);
$("#dialog").one("dialogopen", function (ev) {
var size = $("#dialog").dialog('option', 'sizeCache-switch');
if (size) {
switchDialogResize(null, { size:size });
}
});
$("#dialog").one("dialogclose", function(ev, ui) {
$("#dialog").off("dialogresize", switchDialogResize);
});
},
oneditsave: function() {
var breakpoints = $("#node-input-breakpoint-container").children();
var node = this;
node.breakpoints = [];
breakpoints.each(function (i) {
var breakpoint = $(this);
var r = {};
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);
});
node.breakpoints = node.breakpoints.sort(function (a, b) { return a.input - b.input; });
}
});
</script>
<!-- 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>
<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-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-outputOn">Output on</label>
<select type="text" id="node-input-outputOn" style="width: 200px;">
<option value="both">both changes</option>
<option value="rising">0 to 1 change only</option>
<option value="falling">1 to 0 change only</option>
</select>
</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 discrete-in -->
<script type="text/x-red" data-help-name="discrete-in">
<p>
Discrete input for the Beaglebone Black. Sends a message with payload 0 or 1 on the
first output when the pin changes state, and logs the total time in the active state.
</p>
<p>
Sends a message with a payload of the current 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>
The active state may be set to be high or low: this only affects the calculation
of the active time, not the pin state value sent on the first output.
</p>
<p>
The pin state messages may be generated for both directions of change, or for just 0 to 1
or just 1 to 0 changes. This is useful to generate a single message from a button
press. When using buttons or switches, enable debouncing to improve reliability.
</p>
</script>
<!-- Register discrete-in node -->
<script type="text/javascript">
RED.nodes.registerType('discrete-in', {
category: 'advanced-input', // the palette category
color:"#de7224",
defaults: { // defines the editable properties of the node
pin: { value:"", required:true },
activeLow: { value:false, required:true },
debounce: { value:false, required:true },
outputOn: { value:"both", 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
outputs:2, // set the number of outputs - 0 to n
icon: "BBB.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>
<!-- 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>
<div class="form-row">
<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">
<label style="width: 60%"> 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
total counts and the rate of counts/sec, with scaling.
</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. An input message with topic 'load'
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>
</script>
<!-- Register pulse-in node -->
<script type="text/javascript">
RED.nodes.registerType('pulse-in', {
category: 'advanced-input', // the palette category
color:"#de7224",
defaults: { // defines the editable properties of the node
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() },
updateInterval: { value:2, required:true, validate:RED.validators.number() },
topic: { value:"" },
name: { value:"" }
},
inputs:1, // set the number of inputs - only 0 or 1
outputs:2, // set the number of outputs - 0 to n
icon: "BBB.png", // set the icon (held in public/icons)
label: function() { // sets the default label contents
return this.name || "pulse-in: " + this.pin;
},
labelStyle: function() { // sets the class to apply to the label
return this.name ? "node_label_italic" : "";
}
});
</script>
<!-- Edit dialog for discrete-out -->
<script type="text/x-red" data-template-name="discrete-out">
<div class="form-row">
<label for="node-input-pin"><i class="icon-asterisk"></i>Output 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>
<option value="USR0">User LED 0</option>
<option value="USR1">User LED 1</option>
<option value="USR2">User LED 2</option>
<option value="USR3">User LED 3</option>
</select>
</div>
<div class="form-row">
<label for="node-input-inverting">Inverting</label>
<input type="checkbox" id="node-input-inverting" style="display:inline-block; width:auto; vertical-align:top;">
</div>
<div class="form-row">
<label for="node-input-toggle">Toggle state</label>
<input type="checkbox" id="node-input-toggle" style="display:inline-block; width:auto; vertical-align:top;">
</div>
<div class="form-row">
<label for="node-input-defaultState">Startup as</label>
<select type="text" id="node-input-defaultState" style="width: 80px;">
<option value="0">0</option>
<option value="1">1</option>
</select>
</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 discrete-out -->
<script type="text/x-red" data-help-name="discrete-out">
<p>
Directly control an output pin on the Beaglebone Black.
</p>
<p>
Sets the output pin high or low depending on the payload of the input message. Numeric
payloads > 0.5 are 'high' (1), payloads <= 0.5 are 'low' (0). Other payloads which
evaluate to true are 'high', if not then 'low'. Selecting the Inverting checkbox will
switch the sense of the pin output.
</p>
<p>
If the Toggle state checkbox is checked, the message content is ignored: successive
messages cause the pin to toggle between 0 and 1.
</P>
<p>
The pin will be initially set to the given Startup state until the first message arrives:
the Inverting property is not applied to this value.
</p>
</script>
<!-- Register discrete-out node -->
<script type="text/javascript">
RED.nodes.registerType('discrete-out', {
category: 'advanced-input', // the palette category
color:"#de7224",
defaults: { // defines the editable properties of the node
pin: { value:"", required:true },
inverting: { value:false, required:true },
toggle: { value:false, required:true },
defaultState: { value:"0", required:true },
name: { value:"" }
},
inputs:1, // set the number of inputs - only 0 or 1
outputs:1, // set the number of outputs - 0 to n
icon: "BBB.png", // set the icon (held in public/icons)
align:"right",
label: function() { // sets the default label contents
return this.name || "discrete-out: " + this.pin;
},
labelStyle: function() { // sets the class to apply to the label
return this.name ? "node_label_italic" : "";
}
});
</script>
<!-- Edit dialog for pulse-out -->
<script type="text/x-red" data-template-name="pulse-out">
<div class="form-row">
<label for="node-input-pin"><i class="icon-asterisk"></i>Output 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>
<option value="USR0">User LED 0</option>
<option value="USR1">User LED 1</option>
<option value="USR2">User LED 2</option>
<option value="USR3">User LED 3</option>
</select>
</div>
<div class="form-row">
<label for="node-input-pulseTime"><i class="icon-repeat"></i> Pulse time</label>
<input id="node-input-pulseTime" type="text" style="width: 65px">
<label> seconds</label>
</div>
<div class="form-row">
<label for="node-input-retriggerable">Retriggerable</label>
<input type="checkbox" id="node-input-retriggerable" style="display:inline-block; width:auto; vertical-align:top;">
</div>
<div class="form-row">
<label for="node-input-pulseState">Pulse state</label>
<select type="text" id="node-input-pulseState" style="width: 80px;">
<option value="0">0</option>
<option value="1">1</option>
</select>
</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-out -->
<script type="text/x-red" data-help-name="pulse-out">
<p>
Pulse an output pin on the Beaglebone Black.
</p>
<p>
Pulses the output pin for the set time after receiving an input message, unless the
message has a topic including the text 'time' and a numeric payload. In this case, the
the pulse time will be the value of the payload in seconds. If the time from either
source is < 0.001 seconds, no pulse is generated.
</p>
<p>
In retriggerable mode, a second message within the pulse period will extend the duration
of the pulse by the time value: in non-retriggerable mode, input messages arriving during
the duration of the pulse are ignored.
</p>
<p>
The pin state of the pulse may be set to either 0 or 1: the output pin will switch
back to the other state after the pulse time. An output message is generated each time
the pin changes state: its payload is the new state (0 or 1).
</p>
</script>
<!-- Register pulse-out node -->
<script type="text/javascript">
RED.nodes.registerType('pulse-out', {
category: 'advanced-input', // the palette category
color:"#de7224",
defaults: { // defines the editable properties of the node
pin: { value:"", required:true },
pulseTime: { value:1, required:true, validate:RED.validators.number() },
retriggerable: { value:false, required:true },
pulseState: { value:"1", required:true },
name: { value:"" }
},
inputs:1, // set the number of inputs - only 0 or 1
outputs:1, // set the number of outputs - 0 to n
icon: "BBB.png", // set the icon (held in public/icons)
align:"right",
label: function() { // sets the default label contents
return this.name || "pulse-out: " + this.pin;
},
labelStyle: function() { // sets the class to apply to the label
return this.name ? "node_label_italic" : "";
}
});
</script>

View File

@ -0,0 +1,508 @@
/**
* Copyright 2014 Maxwell R Hadley
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
// Require main module
var RED = require(process.env.NODE_RED_HOME + "/red/red");
// Require bonescript
try {
var bonescript = require("bonescript");
} catch (err) {
require("util").log("[145-BBB-hardware] Error: cannot find module 'bonescript'");
}
// Node constructor for analogue-in
function AnalogueInputNode(n) {
// Create a RED node
RED.nodes.createNode(this, n);
// Store local copies of the node configuration (as defined in the .html)
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
var node = this;
// 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);
}
};
// 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) {
sum = 0;
count = node.averages;
bonescript.analogRead(node.pin, analogReadCallback);
});
} else {
node.error("Unconfigured input pin");
}
}
// Node constructor for discrete-in
function DiscreteInputNode(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
if (n.activeLow) // Set the 'active' state 0 or 1 as appropriate
this.activeState = 0;
else
this.activeState = 1;
this.updateInterval = n.updateInterval * 1000; // How often to send totalActiveTime messages
this.debounce = n.debounce; // Enable switch contact debouncing algorithm
if (n.outputOn === "rising") {
this.activeEdges = [false, true];
} else if (n.outputOn === "falling") {
this.activeEdges = [true, false];
} else if (n.outputOn === "both") {
this.activeEdges = [true, true];
} else {
node.error("Invalid edge type: " + n.outputOn);
}
// Working variables
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
this.debounceTimer = null;
// Define 'node' to allow us to access 'this' from within callbacks
var node = 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 (node.debounce) {
if (node.debouncing === false) {
node.debouncing = true;
node.debounceTimer = setTimeout(function () { bonescript.digitalRead(node.pin, debounceCallback); }, 7);
}
} else {
sendStateMessage(x);
}
}
};
// This function is called approx 7ms after a potential change-of-state which is
// being debounced. Terminate the debounce, and send a message if the state has
// actually changed
var debounceCallback = function (x) {
node.debounceTimer = null;
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();
if (node.currentState === node.activeState) {
node.lastActiveTime = now;
} else if (!isNaN(node.lastActiveTime)) {
node.totalActiveTime += now - node.lastActiveTime;
}
if (node.activeEdges[node.currentState]) {
var msg = {};
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
// message on the second output with the latest value of the total active time, in seconds
var timerCallback = function () {
if (node.currentState === node.activeState) {
var now = Date.now();
node.totalActiveTime += now - node.lastActiveTime;
node.lastActiveTime = now;
}
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, 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. If the topic contains
// 'load' (case insensitive) 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/i) < 0 || isFinite(ipMsg.payload) == false) {
node.totalActiveTime = 0;
} else {
node.totalActiveTime = Number(ipMsg.payload);
}
if (node.currentState === node.activeState) {
node.lastActiveTime = Date.now();
}
// On startup, send an initial activeTime message, but only send an
// initial currentState message if we are in both edges active mode
if (node.starting) {
node.starting = false;
var msg;
if (node.activeEdges[0] && node.activeEdges[1]) {
msg = [{topic:node.topic}, {topic:node.topic}];
msg[0].payload = node.currentState;
} else {
msg = [null, {topic:node.topic}];
}
msg[1].payload = node.totalActiveTime;
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
bonescript.detachInterrupt(node.pin);
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.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
// pin state on the first output
if (bonescript.attachInterrupt(node.pin, true, bonescript.CHANGE, interruptCallback)) {
node.interruptAttached = true;
node.on("input", inputCallback);
node.intervalId = setInterval(timerCallback, node.updateInterval);
} else {
node.error("Failed to attach interrupt");
}
setTimeout(function () { node.emit("input", {}); }, 50);
});
});
} else {
node.error("Unconfigured input pin");
}
}
// Node constructor for pulse-in
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 output messages
this.countType = n.countType; // Sets either 'edge' or 'pulse' counting
this.countUnit = n.countUnit; // Scaling appling to count output
this.countRate = n.countRate; // Scaling applied to rate output
// Working variables
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]];
// Define 'node' to allow us to access 'this' from within callbacks
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) {
if (x.value !== undefined) {
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) {
if (String(msg.topic).search(/load/i) < 0 || isFinite(msg.payload) == false) {
node.pulseCount = 0;
} else {
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 now = process.hrtime();
var lastTime = node.pulseTime[1][0] - node.pulseTime[0][0] + (node.pulseTime[1][1] - node.pulseTime[0][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 }];
msg[0].payload = node.countUnit * node.pulseCount;
// 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);
};
// 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
bonescript.detachInterrupt(node.pin);
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 an interrupt handler to the pin. If we succeed,
// set the input event and interval handlers
var interruptType;
if (node.countType === "pulse") {
// interruptType = bonescript.FALLING; <- doesn't work in v0.2.4
interruptType = bonescript.RISING;
} 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");
}
}
// Node constructor for discrete-out
function DiscreteOutputNode(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.defaultState = Number(n.defaultState); // What state to set up as
this.inverting = n.inverting;
this.toggle = n.toggle;
// Working variables
this.currentState = this.defaultState;
var node = this;
// If the input message paylod is numeric, values > 0.5 are 'true', otherwise use
// the truthiness of the payload. Apply the inversion flag before setting the output
var inputCallback = function (msg) {
var newState;
if (node.toggle) {
newState = node.currentState === 0 ? 1 : 0;
} else {
if (isFinite(Number(msg.payload))) {
newState = Number(msg.payload) > 0.5 ? true : false;
} else if (msg.payload) {
newState = true;
} else {
newState = false;
}
if (node.inverting) {
newState = !newState;
}
}
bonescript.digitalWrite(node.pin, newState ? 1 : 0);
node.send({ topic:node.topic, payload:newState });
node.currentState = newState;
};
// If we have a valid pin, set it as an output and set the default 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", "USR0", "USR1", "USR2", "USR3"].indexOf(node.pin) >= 0) {
// Don't set up interrupts & intervals until after the close event handler has been installed
bonescript.detachInterrupt(node.pin);
process.nextTick(function () {
bonescript.pinMode(node.pin, bonescript.OUTPUT);
node.on("input", inputCallback);
setTimeout(function () { bonescript.digitalWrite(node.pin, node.defaultState); }, 50);
});
} else {
node.error("Unconfigured output pin");
}
}
// Node constructor for pulse-out
function PulseOutputNode(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.pulseState = Number(n.pulseState); // What state the pulse will be..
this.defaultState = this.pulseState === 1 ? 0 : 1;
this.retriggerable = n.retriggerable;
this.pulseTime = n.pulseTime * 1000; // Pulse width in milliseconds
// Working variables
this.pulseTimer = null; // Non-null while a pulse is being generated
var node = this;
// Generate a pulse in response to an input message. If the topic includes the text
// 'time' (case insensitive) and the payload is numeric, use this value as the
// pulse time. Otherwise use the value from the properties dialog.
// If the resulting pulse time is < 1ms, do nothing.
// If the pulse mode is not retriggerable, then if no pulseTimer is active, generate
// a pulse. If the pulse mode is retriggerable, and a pulseTimer is active, cancel it.
// If no timer is active, set the pulse output. In both cases schedule a new pulse
// timer.
var inputCallback = function (msg) {
var time = node.pulseTime;
if (String(msg.topic).search(/time/i) >= 0 && isFinite(msg.payload)) {
time = msg.payload * 1000;
}
if (time >= 1) {
if (node.retriggerable === false) {
if (node.pulseTimer === null) {
node.pulseTimer = setTimeout(endPulseCallback, time);
bonescript.digitalWrite(node.pin, node.pulseState);
node.send({ topic:node.topic, payload:node.pulseState });
}
} else {
if (node.pulseTimer !== null) {
clearTimeout(node.pulseTimer);
} else {
bonescript.digitalWrite(node.pin, node.pulseState);
node.send({ topic:node.topic, payload:node.pulseState });
}
node.pulseTimer = setTimeout(endPulseCallback, time);
}
}
};
// At the end of the pulse, restore the default state and set the timer to null
var endPulseCallback = function () {
node.pulseTimer = null;
bonescript.digitalWrite(node.pin, node.defaultState);
node.send({ topic:node.topic, payload:node.defaultState });
};
// If we have a valid pin, set it as an output and set the default 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", "USR0", "USR1", "USR2", "USR3"].indexOf(node.pin) >= 0) {
// Don't set up interrupts & intervals until after the close event handler has been installed
bonescript.detachInterrupt(node.pin);
process.nextTick(function () {
bonescript.pinMode(node.pin, bonescript.OUTPUT);
node.on("input", inputCallback);
// Set the pin to the default stte once the dust settles
setTimeout(endPulseCallback, 50);
});
} else {
node.error("Unconfigured output pin");
}
}
// Register the nodes by name. This must be called before overriding any of the Node functions.
RED.nodes.registerType("analogue-in", AnalogueInputNode);
RED.nodes.registerType("discrete-in", DiscreteInputNode);
RED.nodes.registerType("pulse-in", PulseInputNode);
RED.nodes.registerType("discrete-out", DiscreteOutputNode);
RED.nodes.registerType("pulse-out", PulseOutputNode);
// On close, detach the interrupt (if we attached one) and clear any active timers
DiscreteInputNode.prototype.close = function () {
if (this.interruptAttached) {
bonescript.detachInterrupt(this.pin);
}
if (this.intervalId !== null) {
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)
PulseInputNode.prototype.close = function () {
if (this.interruptAttached) {
bonescript.detachInterrupt(this.pin);
}
if (this.intervalId !== null) {
clearInterval(this.intervalId);
}
};
// On close, clear an active pulse timer
PulseOutputNode.prototype.close = function () {
if (this.pulseTimer !== null) {
clearTimeout(this.pulseTimer);
}
};