node-red-contrib-tank-volume
Version:
A Node-RED node to calculate the volume of different tank types
463 lines (437 loc) • 24.2 kB
HTML
<!--
Copyright 2021, Bart Butenaers
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.
-->
<script type="text/javascript">
var tankTypes = [
{v:"none" , t:""},
{v:"horiz_cylin" , t:"Horizontal cylinder"},
{v:"vert_cylin" , t:"Vertical cylinder"},
{v:"rect_prism" , t:"Rectangular prism (box)"},
{v:"cone_top" , t:"Cone top"},
{v:"cone_bottom" , t:"Cone bottom"},
{v:"horiz_caps" , t:"Horizontal capsule"},
{v:"vert_caps" , t:"Vertical capsule"},
{v:"inv_piram" , t:"Inverse piramid"},
{v:"horiz_oval" , t:"Horizontal oval (elliptical)"},
{v:"vert_oval" , t:"Vertical oval (elliptical)"},
{v:"frustrum" , t:"Frustum (truncated cone)"},
{v:"sphere" , t:"Sphere"},
{v:"horiz_ellip" , t:"Horizontal elliptical"},
{v:"horiz_stad" , t:"Horizontal stadium"},
{v:"vert_stad" , t:"Vertical stadium"},
{v:"custom_table", t:"Custom (table)"}
];
// The resizeDialog code is based on the Node-RED 20-inject.html file
function resizeDialog(size) {
// Get the total height available in the popup dialog (for this node's config screen)
size = size || { height: $(".red-ui-tray-content form").height() }
// Get the already used heigh, i.e. the height of all the first level DIV elements (i.e. not the nested DIV elements).
// This is the space used by the top level form-row DIV elements, e.g. to show the "Name" input field.
// Note that the current tabsheet content (i.e. #node-energy-tabs-content) space is ignored, since that is the one we need to calculate.
var rows = $("#dialog-form>div:not(#node-tank-tabs-content):visible");
var height = size.height;
for (var i=0; i<rows.length; i++) {
height -= $(rows[i]).outerHeight(true);
}
var editorRow = $("#dialog-form>div#node-tank-tabs-content");
height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
// Set the current visible editableList container height to the available height.
// The catch will be triggered when the dialog is opened: every addTab function (in the oneditprepare) will trigger it,
// but at that moment the editableList elements are not initialized yet (resulting in an exception).
// That is however not a problem, since immediately afterwards the oneditresize function will trigger this code again...
try {
$(".auto-height-container:visible").editableList('height',height);
}
catch(err) {}
}
RED.nodes.registerType('tank-volume',{
category: 'smart home',
color: '#E9967A',
defaults: {
name: {value:""},
tankType: {value:""},
inputField: {value:"payload"},
outputField: {value:"payload"},
measurement: {value:"above"},
inputUnit1: {value:"cm"},
inputUnit2: {value:"l"},
outputUnit: {value:"l"},
topLimit: {value:0},
bottomLimit: {value:0},
diameter: {value: 0},
length: {value: 0},
width: {value: 0},
height: {value: 0},
length2: {value: 0},
width2: {value: 0},
height2: {value: 0},
coneHeight: {value: 0},
cylinderHeight: {value: 0},
diameterTop: {value: 0},
diameterBottom: {value: 0},
customTable: {value: []}
},
inputs:1,
outputs:1,
icon: "font-awesome/fa-battery-full",
label: function() {
var tankTypeText = "";
// Get the tank type text, based on the tank type value
for (var i = 0; i < tankTypes.length; i++) {
if (tankTypes[i].v === this.tankType) {
tankTypeText = tankTypes[i].t;
break;
}
}
return this.name || tankTypeText || "Tank volume";
},
oneditprepare: function() {
var node = this;
// Show the inputField value in a typedinput element (dropdown with only 'msg')
$("#node-input-inputField").typedInput({
default: 'msg',
types:['msg']
});
// Show the outputField value in a typedinput element (dropdown with only 'msg')
$("#node-input-outputField").typedInput({
default: 'msg',
types:['msg']
});
// Dynamically show all tank type options in the dropdown
for (var i = 0; i < tankTypes.length; i++) {
var value = tankTypes[i].v;
var text = tankTypes[i].t;
$('#node-input-tankType').append($("<option></option>").attr("value", value).text(text));
}
// Make sure the selected value is also selected in the <select> tag
$('#node-input-tankType').val(this.tankType);
var imageElement = $("#tank-image");
// When the tank type value changes, only the corresponding field(s) input fields should be displayed
$("#node-input-tankType").change(function() {
if (this.value && this.value !== "" && this.value !== "none" && this.value !== "custom_table") {
// Show the image of the selected tank
imageElement.attr("src", 'tank-volume/' + this.value + '.png');
imageElement.show();
}
else {
imageElement.attr("src", '');
imageElement.hide();
}
// Hide all tank type related properties
$(".tankType-property").hide();
// Show the properties related to the selected tank type
switch(this.value) {
case "horiz_cylin":
$("#node-diameter-div").show();
$("#node-length-div").show();
break;
case "vert_cylin":
$("#node-diameter-div").show();
$("#node-height-div").show();
break;
case "rect_prism":
$("#node-width-div").show();
$("#node-length-div").show();
$("#node-height-div").show();
break;
case "cone_top":
$("#node-coneHeight-div").show();
$("#node-cylinderHeight-div").show();
$("#node-diameterTop-div").show();
$("#node-diameterBottom-div").show();
break;
case "cone_bottom":
$("#node-coneHeight-div").show();
$("#node-cylinderHeight-div").show();
$("#node-diameterTop-div").show();
$("#node-diameterBottom-div").show();
break;
case "inv_piram":
$("#node-width-div").show();
$("#node-length-div").show();
$("#node-height-div").show();
$("#node-width2-div").show();
$("#node-length2-div").show();
$("#node-height2-div").show();
break;
case "horiz_caps":
$("#node-diameter-div").show();
$("#node-length-div").show();
break;
case "vert_caps":
$("#node-diameter-div").show();
$("#node-length-div").show();
break;
case "horiz_oval":
case "vert_oval":
$("#node-width-div").show();
$("#node-length-div").show();
$("#node-height-div").show();
break;
case "frustrum":
$("#node-height-div").show();
$("#node-diameterTop-div").show();
$("#node-diameterBottom-div").show();
break;
case "sphere":
$("#node-diameter-div").show();
break;
case "horiz_ellip":
$("#node-diameter-div").show();
$("#node-length-div").show();
break;
case "horiz_stad":
$("#node-width-div").show();
$("#node-length-div").show();
$("#node-height-div").show();
break;
case "vert_stad":
$("#node-width-div").show();
$("#node-length-div").show();
$("#node-height-div").show();
break;
case "custom_table":
$("#node-table-div").show();
break;
}
// The "Custom tank" tabsheet should be enabled only when custom tank option is selected.
// Note that enabling/disabling tabsheets is not supported by Node-RED (see https://discourse.nodered.org/t/disable-a-tabsheet-in-config-screen/32031)
if (this.value === "custom_table") {
$("#red-ui-tab-node-tank-tab-custom").css('pointer-events', "");
$("#red-ui-tab-node-tank-tab-custom").css('opacity', "");
}
else {
$("#red-ui-tab-node-tank-tab-custom").css('pointer-events', "none");
$("#red-ui-tab-node-tank-tab-custom").css('opacity', "0.4");
}
});
$("#node-input-tankType").change();
// Show tabsheets
node.tabs = RED.tabs.create({
id: "node-tank-tabs",
onchange: function(tab) {
// Show only the content (i.e. the children) of the selected tabsheet, and hide the others
$("#node-tank-tabs-content").children().hide();
$("#" + tab.id).show();
// Make sure that the editableList on this tab will fill the entire available area
resizeDialog();
}
});
node.tabs.addTab({
id: "node-tank-tab-general",
label: "General"
});
node.tabs.addTab({
id: "node-tank-tab-tank",
label: "Tank"
});
node.tabs.addTab({
id: "node-tank-tab-custom",
label: "Custom tank"
});
var customTableList = $("#node-input-custom-container").css('min-height','200px').editableList({
// Apply css to the parent div to have margins, to make sure the percentages of the headers match those of the html elements below.
// Thanks to hotNipi for this tip !!!
header: $("<div>").css({"margin-left":"28px","margin-right":"10x"}).append($.parseHTML(
"<div style='width:40%; display: inline-grid'><b>Height</b></div>" +
"<div style='width:40%; display: inline-grid'><b>Volume</b></div>")),
addItem: function(container, i, customTableRow) {
// Add a new row to the editableList
var row = $('<div/>').appendTo(container);
// Column 1 : Add an input field (type number) to the new row, that represents the height
var heightField = $('<input/>',{class:"node-input-custom-height",type:"number"}).css({"width":"40%","margin-left":"5px","margin-right":"5px"}).appendTo(row);
heightField.val(customTableRow.height || 0);
// Column 2 : Add an input field (type number) to the new row, that represents the volume (corresponding to that height)
var volumeField = $('<input/>',{class:"node-input-custom-volume",type:"number"}).css({"width":"40%","margin-left":"5px","margin-right":"5px"}).appendTo(row);
volumeField.val(customTableRow.volume || 0);
},
removable: true,
sortable: true
});
// Show all the options (stored in this node) into the editableList
if (node.customTable) {
node.customTable.forEach(function (customTableRow, index) {
customTableList.editableList('addItem', {height:customTableRow.height, volume:customTableRow.volume});
});
}
},
oneditsave: function() {
var node = this;
// Copy all the options from the editableList to this node
node.customTable = [];
var customTableList = $("#node-input-custom-container").editableList('items');
customTableList.each(function(i) {
var option = $(this);
var height = option.find(".node-input-custom-height").val();
var volume = option.find(".node-input-custom-volume").val();
node.customTable.push({height:height, volume:volume});
});
},
oneditresize: function(size) {
resizeDialog(size);
}
});
</script>
<script type="text/x-red" data-template-name="tank-volume">
<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>
</br>
<div class="form-row">
<!-- Tabsheets -->
<ul style="background: #fff; margin-bottom: 20px;" id="node-tank-tabs"></ul>
</div>
<div id="node-tank-tabs-content" style="min-height: 150px">
<!-- Content of all tabsheets -->
<div id="node-tank-tab-general" class="node-tank-tab-content">
<div class="form-row">
<label for="node-input-inputField"><i class="fa fa-sign-in"></i> Input field</label>
<input id="node-input-inputField" type="text" style="width: 70%">
</div>
<div class="form-row">
<label for="node-input-outputField"><i class="fa fa-sign-out"></i> Output field</label>
<input id="node-input-outputField" type="text" style="width: 70%">
</div>
<div class="form-row">
<label for="node-input-measurement"><i class="fa fa-download"></i> Measure</label>
<select id="node-input-measurement" style="width: 70%">
<option value="fluid">Height of fluid</option>
<option value="above">Height above fluid</option>
</select>
</div>
<div class="form-row">
<label for="node-input-inputUnit1"><i class="fa fa-sign-in"></i> Input unit 1</label>
<select id="node-input-inputUnit1" style="width: 70%">
<option value="mm">Millimeters</option>
<option value="cm">Centimeters</option>
<option value="in">Inches</option>
</select>
</div>
<div class="form-row">
<label for="node-input-inputUnit2"><i class="fa fa-sign-in"></i> Input unit 2</label>
<select id="node-input-inputUnit2" style="width: 70%">
<option value="cm3">Cubic centimeters</option>
<option value="l">Litres</option>
<option value="in3">Cubic inches</option>
<option value="gal">Gallons (US)</option>
</select>
</div>
<div class="form-row">
<label for="node-input-outputUnit"><i class="fa fa-sign-out"></i> Output unit</label>
<select id="node-input-outputUnit" style="width: 70%">
<option value="cm3">Cubic centimeters</option>
<option value="l">Litres</option>
<option value="in3">Cubic inches</option>
<option value="gal">Gallons (US)</option>
</select>
</div>
<div class="form-row">
<label for="node-input-topLimit"><i class="fa fa-level-down"></i> Top limit</label>
<input id="node-input-topLimit" type="number" style="width: 70%">
</div>
<div class="form-row">
<label for="node-input-bottomLimit"><i class="fa fa-level-up"></i> Bottom limit</label>
<input id="node-input-bottomLimit" type="number" style="width: 70%">
</div>
</div>
<div id="node-tank-tab-tank" class="node-tank-tab-content">
<div class="form-row">
<label for="node-input-tankType"><i class="fa fa-battery-full"></i> Tank type</label>
<select id="node-input-tankType" style="width: 70%">
<!-- The options will be added programmatically -->
</select>
</div>
<div class="form-row tankType-property" id="node-diameter-div">
<label for="node-input-diameter"><i class="fa fa-cog"></i> Diameter</label>
<input id="node-input-diameter" type="number" style="width: 70%">
</div>
<div class="form-row tankType-property" id="node-length-div">
<label for="node-input-length"><i class="fa fa-cog"></i> Length</label>
<input id="node-input-length" type="number" style="width: 70%">
</div>
<div class="form-row tankType-property" id="node-width-div">
<label for="node-input-width"><i class="fa fa-cog"></i> Width</label>
<input id="node-input-width" type="number" style="width: 70%">
</div>
<div class="form-row tankType-property" id="node-height-div">
<label for="node-input-height"><i class="fa fa-cog"></i> Height</label>
<input id="node-input-height" type="number" style="width: 70%">
</div>
<div class="form-row tankType-property" id="node-length2-div">
<label for="node-input-length2"><i class="fa fa-cog"></i> Length 2</label>
<input id="node-input-length2" type="number" style="width: 70%">
</div>
<div class="form-row tankType-property" id="node-width2-div">
<label for="node-input-width2"><i class="fa fa-cog"></i> Width 2</label>
<input id="node-input-width2" type="number" style="width: 70%">
</div>
<div class="form-row tankType-property" id="node-height2-div">
<label for="node-input-height2"><i class="fa fa-cog"></i> Height 2</label>
<input id="node-input-height2" type="number" style="width: 70%">
</div>
<div class="form-row tankType-property" id="node-coneHeight-div">
<label for="node-input-coneHeight"><i class="fa fa-cog"></i> Cone height</label>
<input id="node-input-coneHeight" type="number" style="width: 70%">
</div>
<div class="form-row tankType-property" id="node-cylinderHeight-div">
<label for="node-input-cylinderHeight"><i class="fa fa-cog"></i> Cyl Height</label>
<input id="node-input-cylinderHeight" type="number" style="width: 70%">
</div>
<div class="form-row tankType-property" id="node-diameterTop-div">
<label for="node-input-diameterTop"><i class="fa fa-cog"></i> Diam top</label>
<input id="node-input-diameterTop" type="number" style="width: 70%">
</div>
<div class="form-row tankType-property" id="node-diameterBottom-div">
<label for="node-input-diameterBottom"><i class="fa fa-cog"></i> Diam bot</label>
<input id="node-input-diameterBottom" type="number" style="width: 70%">
</div>
<div class="form-row tankType-property" id="node-table-div">
<label for="node-input-tableLabel"><i class="fa fa-table"></i> Table</label>
<label id="node-input-tableLabel" style="width: 70%"> Configure the table in the "Custom tank" tabsheet</label>
</div>
</br>
<div class="form-row" style="width: 100%; text-align: center;">
<div style="display: inline-block;">
<img id="tank-image" src="" alt="Tank image" width="250" height="250">
</div>
</div>
</div>
<div id="node-tank-tab-custom" class="node-tank-tab-content">
<div class="form-row">
<!-- Table with custom height/volume pairs -->
<ol id="node-input-custom-container" class="auto-height-container"></ol>
</div>
</div>
</div>
</script>
<script type="text/x-red" data-help-name="tank-volume">
<p>A node to calculate a tank volume.</p>
<p><strong>Input field:</strong><br/>
Specify the field in the input message message where the settings will arrive.</p>
<p><strong>Output field:</strong><br/>
Specify the field in the output message message where the result will be send.</p>
<p><strong>Measurement:</strong><br/>
Specify how the liquid level is being measured.</p>
<p><strong>Input unit 1:</strong><br/>
Specify the units of the tank dimensions.</p>
<p><strong>Input unit 2:</strong><br/>
Specify the units of the custom tank volumes (see tabsheet "Custom tank").</p>
<p><strong>Output unit:</strong><br/>
Specify the units of the calculated volume.</p>
<p><strong>Top limit:</strong><br/>
Specify which distance - from the top of the tank - should not be exceeded, i.e. the fluid level should always be below this level.</p>
<p><strong>Bottom limit:</strong><br/>
Specify which distance - from the bottom of the tank - should not be exceeded, i.e. the fluid level should always be above this level.</p>
<p><strong>Tank type:</strong><br/>
For every tank type, a series of parameters need to be specified. ALL these parameters need to be specified in the same unit (cm, inch, ...), as specified on the config screen! Note that these parameters can be overwritten by the input message (like width, height, lenght, diameter, ...).</p>
</script>