node-red-contrib-cip-ethernet-ip
Version:
A Node-RED node to interact with Allen Bradley / Rockwell PLCs using the EtherNet/IP Protocol
754 lines (664 loc) • 27 kB
HTML
<!--
Copyright: (c) 2016-2020, ST-One Ltda., Guilherme Francescon Cittolin <guilherme@st-one.io>
GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
-->
<style>
.eth-ip-vars-prog-title {
/*min-height: 35px;*/
background-color: #f3f3f3;
padding: 5px;
}
.eth-ip-vars-prog-handle {
padding: 5px;
color: #ddd;
}
.eth-ip-vars-prog-chevron {
margin: 3px 5px;
width: 10px;
}
.node-input-variables-container-row,
.node-input-variables-container-row li {
padding: 0;
}
input.eth-ip-vars-var-name {
width: 50% ;
}
select.eth-ip-vars-var-type {
width: 40% ;
margin-left: 5px;
}
.node-input-variables-container-row .red-ui-editableList-container{
padding: 0;
min-height: 300px;
}
.eth-ip-vars-prog-item .red-ui-editableList-container{
border-radius: 0;
border: none;
height: auto ;
min-height: unset;
}
.eth-ip-vars-var-list {
padding: 0 7px;
}
</style>
<script type="text/x-red" data-template-name="eth-ip endpoint">
<div class="form-row">
<ul style="background: #fff; min-width: 600px; margin-bottom: 20px;" id="node-config-ethip-endpoint-tabs"></ul>
</div>
<div id="node-config-ethip-endpoint-tabs-content" style="min-height: 170px; height: 95%">
<div id="ethip-endpoint-tab-connection" style="display:none">
<div class="form-row">
<label for="node-config-input-address"><i class="fa fa-globe"></i> <span data-i18n="ethip.endpoint.label.address"></span></label>
<input class="input-append-left" type="text" id="node-config-input-address" data-i18n="[placeholder]ethip.endpoint.label.address" style="width: 40%;">
<label for="node-config-input-slot" style="margin-left: 10px; width: 35px; "> <span data-i18n="ethip.endpoint.label.slot"></span></label>
<input type="text" id="node-config-input-slot" data-i18n="[placeholder]ethip.endpoint.label.port" style="width: 50px">
</div>
<div class="form-row">
<label for="node-config-input-cycletime"><i class="fa fa-refresh"></i> <span data-i18n="ethip.endpoint.label.cycletime"></span></label>
<input type="text" id="node-config-input-cycletime" data-i18n="[placeholder]ethip.endpoint.label.cycletime" style="width: 60px;"> <span>ms</span>
</div>
<div class="form-row">
<label for="node-config-input-name"><i class="fa fa-tag"></i> <span data-i18n="ethip.label.name"></span></label>
<input type="text" id="node-config-input-name" data-i18n="[placeholder]ethip.label.name">
</div>
</div>
<div id="ethip-endpoint-tab-variables" style="display:none; height: 90%;">
<div class="form-row eth-ip-vars-button-group" style="margin-bottom:0; height: 5%">
<a href="#" class="editor-button editor-button-small eth-ip-vars-btn-clean" style="margin: 4px; float: right;"><i class="fa fa-trash-o"></i> <span data-i18n="ethip.endpoint.label.variables.clean"></span></a>
<a href="#" class="editor-button editor-button-small eth-ip-vars-btn-add-prog" style="margin: 4px; float: right;"><i class="fa fa-plus"></i> <span data-i18n="ethip.endpoint.label.variables.add"></span></a>
<a href="#" class="editor-button editor-button-small eth-ip-vars-btn-expand" style="margin: 4px; float: right;"><i class="fa fa-angle-double-down"></i></a>
<a href="#" class="editor-button editor-button-small eth-ip-vars-btn-collapse" style="margin: 4px; float: right;"><i class="fa fa-angle-double-up"></i></a>
<label><b><span data-i18n="ethip.endpoint.label.variables.list"></b></span></label>
</div>
<div class="form-row node-input-variables-container-row" style="margin-bottom: 0px; max-height: 100%; overflow-y: auto">
<ol id="node-config-input-variables-container" style="min-height: 250px; min-width: 450px;"></ol>
</div>
<div class="form-row">
<a href="#" class="editor-button editor-button-small eth-ip-vars-btn-export" style="margin: 4px; float: right"><i class="fa fa-download"></i> <span data-i18n="ethip.endpoint.label.variables.export"></span></a>
<input type="file" id="node-config-ethip-endpoint-var-import" style="display: none"/>
<a href="#" class="editor-button editor-button-small eth-ip-vars-btn-import" style="margin: 4px; float: right"><i class="fa fa-upload"></i> <span data-i18n="ethip.endpoint.label.variables.import"></span></a>
</div>
</div>
</div>
</script>
<script type="text/x-red" data-help-name="eth-ip endpoint">
<p>Represents a PLC connection</p>
<p>This node was created as part of the <a href="https://st-one.io" target="_blank">ST-One</a> project</p>
</script>
<script type="text/javascript">
/*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */
var saveAs=saveAs||function(e){"use strict";if(typeof e==="undefined"||typeof navigator!=="undefined"&&/MSIE [1-9]\./.test(navigator.userAgent)){return}var t=e.document,n=function(){return e.URL||e.webkitURL||e},r=t.createElementNS("http://www.w3.org/1999/xhtml","a"),o="download"in r,a=function(e){var t=new MouseEvent("click");e.dispatchEvent(t)},i=/constructor/i.test(e.HTMLElement)||e.safari,f=/CriOS\/[\d]+/.test(navigator.userAgent),u=function(t){(e.setImmediate||e.setTimeout)(function(){throw t},0)},s="application/octet-stream",d=1e3*40,c=function(e){var t=function(){if(typeof e==="string"){n().revokeObjectURL(e)}else{e.remove()}};setTimeout(t,d)},l=function(e,t,n){t=[].concat(t);var r=t.length;while(r--){var o=e["on"+t[r]];if(typeof o==="function"){try{o.call(e,n||e)}catch(a){u(a)}}}},p=function(e){if(/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(e.type)){return new Blob([String.fromCharCode(65279),e],{type:e.type})}return e},v=function(t,u,d){if(!d){t=p(t)}var v=this,w=t.type,m=w===s,y,h=function(){l(v,"writestart progress write writeend".split(" "))},S=function(){if((f||m&&i)&&e.FileReader){var r=new FileReader;r.onloadend=function(){var t=f?r.result:r.result.replace(/^data:[^;]*;/,"data:attachment/file;");var n=e.open(t,"_blank");if(!n)e.location.href=t;t=undefined;v.readyState=v.DONE;h()};r.readAsDataURL(t);v.readyState=v.INIT;return}if(!y){y=n().createObjectURL(t)}if(m){e.location.href=y}else{var o=e.open(y,"_blank");if(!o){e.location.href=y}}v.readyState=v.DONE;h();c(y)};v.readyState=v.INIT;if(o){y=n().createObjectURL(t);setTimeout(function(){r.href=y;r.download=u;a(r);h();c(y);v.readyState=v.DONE});return}S()},w=v.prototype,m=function(e,t,n){return new v(e,t||e.name||"download",n)};if(typeof navigator!=="undefined"&&navigator.msSaveOrOpenBlob){return function(e,t,n){t=t||e.name||"download";if(!n){e=p(e)}return navigator.msSaveOrOpenBlob(e,t)}}w.abort=function(){};w.readyState=w.INIT=0;w.WRITING=1;w.DONE=2;w.error=w.onwritestart=w.onprogress=w.onwrite=w.onabort=w.onerror=w.onwriteend=null;return m}(typeof self!=="undefined"&&self||typeof window!=="undefined"&&window||this.content);if(typeof module!=="undefined"&&module.exports){module.exports.saveAs=saveAs}else if(typeof define!=="undefined"&&define!==null&&define.amd!==null){define("FileSaver.js",function(){return saveAs})}
</script>
<script type="text/javascript">
(function(){
//["BOOL", "SINT", "INT", "DINT", "LINT", "USINT", "UINT", "UDINT", "REAL", "LREAL", "STIME", "DATE", "TIME_AND_DAY", "DATE_AND_STRING", "STRING", "WORD", "DWORD", "BIT_STRING", "LWORD", "STRING2", "FTIME", "LTIME", "ITIME", "STRINGN", "SHORT_STRING", "TIME", "EPATH", "ENGUNIT", "STRINGI", "STRUCT"];
var dataTypes = ["", "BOOL", "SINT", "INT", "DINT", "REAL"];
function debuglog(){
//console.log(arguments);
}
function generateVarTable(){
var vartable = {};
var progItems = $("#node-config-input-variables-container").editableList('items');
progItems.each(function (i){
var progItem = $(this);
var progName = progItem.find('.eth-ip-vars-prog-name').val() || '';
vartable[progName] = {};
var varItems = progItem.find('.eth-ip-vars-var-list').editableList('items');
varItems.each(function (j){
var varItem = $(this);
var varName = varItem.find('.eth-ip-vars-var-name').val();
var varType = varItem.find('.eth-ip-vars-var-type').val();
vartable[progName][varName] = {
type: varType
};
});
});
debuglog('generateVarTable', vartable);
return vartable;
}
RED.nodes.registerType('eth-ip endpoint', {
category: 'config',
defaults: {
address: {
value: "",
validate: RED.validators.regex(/^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/)
},
slot: {
value: "0",
validate: RED.validators.number()
},
cycletime: {
value: 500
},
name: {
value: ""
},
vartable: {
value: {
'': {
'': {
type: ''
}
}
}
}
},
label: function() {
return this.name || this.address + ":" + this.slot;
},
oneditprepare: function() {
var self = this;
//labels (i18n)
var labelName = this._("ethip.endpoint.label.variables.name");
var labelAddr = this._("ethip.endpoint.label.variables.addr");
var labelAdd = this._("ethip.endpoint.label.variables.add");
var labelDel = this._("ethip.endpoint.label.variables.del");
var labelGlobal = this._("ethip.endpoint.label.variables.global");
var labelProgram = this._("ethip.endpoint.label.variables.program");
var labelTag = this._("ethip.endpoint.label.variables.tag");
//elms
var progContainer = $("#node-config-input-variables-container");
var btnCollapse = $('.eth-ip-vars-btn-collapse');
var btnExpand = $('.eth-ip-vars-btn-expand');
var btnAddProg = $('.eth-ip-vars-btn-add-prog');
var btnClean = $('.eth-ip-vars-btn-clean');
var btnImport = $('.eth-ip-vars-btn-import');
var btnExport = $('.eth-ip-vars-btn-export');
// Prepare tabs
var tabs = RED.tabs.create({
id: "node-config-ethip-endpoint-tabs",
onchange: function(tab) {
$("#node-config-ethip-endpoint-tabs-content").children().hide();
$("#" + tab.id).show();
}
});
tabs.addTab({
id: "ethip-endpoint-tab-connection",
label: this._("ethip.endpoint.label.tabs.connection")
});
tabs.addTab({
id: "ethip-endpoint-tab-variables",
label: this._("ethip.endpoint.label.tabs.variables")
});
setTimeout(function() {
tabs.resize()
}, 0);
function cleanTable(skipGlobal){
progContainer.empty();
if(skipGlobal) return;
progContainer.editableList('addItem', {name: '', isGlobal: true, vars: {}});
}
// export
function exportCSV() {
var vars = generateVarTable();
var lines = [];
Object.keys(vars).forEach(function (progName){
Object.keys(vars[progName]).forEach(function (varName) {
var obj = vars[progName][varName];
var line = [progName, varName, obj.type];
lines.push(line.join(';'));
});
});
saveAs(new Blob([lines.join('\r\n')]), 'ethip-endpoint' + (self.name ? '_' + self.name : '') + '.csv');
}
// import
function importCSV(e) {
var file = e.target.files[0];
if (!file) {
return;
}
var reader = new FileReader();
reader.onload = function (e) {
var res = {}, i, fields, resKeys;
var contents = e.target.result || '';
var lines = contents.split(/[\r\n]+/);
if (!lines.length) {
alert('file is empty!');
return;
}
debuglog('importCSV lines', lines);
for (i = 0; i < lines.length; i++) {
lines[i] = lines[i].trim();
if (lines[i] == '') continue;
fields = lines[i].split(/[\t;]/);
if (fields.length < 2) {
alert('line must have at least two parameters, program and tag name');
return;
}
res[fields[0]] = res[fields[0]] || {};
res[fields[0]][fields[1]] = {
type: fields[2]
}
}
debuglog('importCSV result', res)
resKeys = Object.keys(res);
if (resKeys.length) {
cleanTable(true);
resKeys.forEach(function(elm){
progContainer.editableList('addItem', {name: elm, isGlobal: elm === '', vars: res[elm]});
});
}
};
reader.readAsText(file);
}
// Buttons
btnCollapse.click(function(evt) {
//TODO replace with the right selectors
progContainer.find(".eth-ip-vars-var-container").slideUp();
progContainer.find(".eth-ip-vars-prog-title>.eth-ip-vars-prog-chevron").css({ "transform": "rotate(-90deg)" });
evt.preventDefault();
});
btnExpand.click(function(evt) {
//TODO replace with the right selectors
progContainer.find(".eth-ip-vars-var-container").slideDown();
progContainer.find(".eth-ip-vars-prog-title>.eth-ip-vars-prog-chevron").css({ "transform": "" });
evt.preventDefault();
});
btnAddProg.click(function(evt) {
progContainer.editableList('addItem', {});
evt.preventDefault();
});
btnClean.click(function(evt) {
cleanTable();
evt.preventDefault();
});
btnExport.click(exportCSV);
btnImport.click(function(evt) {
$('#node-config-ethip-endpoint-var-import').click();
});
$('#node-config-ethip-endpoint-var-import').on('change', importCSV);
// toggle slide tab group content
var titleToggle = function (content, chevron) {
return function (evt) {
if (content.is(":visible")) {
content.slideUp();
chevron.css({ "transform": "rotate(-90deg)" });
//content.addClass('nr-db-sb-collapsed');
}
else {
content.slideDown();
chevron.css({ "transform": "" });
//content.removeClass('nr-db-sb-collapsed');
}
};
};
progContainer.editableList({
sortable: ".eth-ip-vars-prog-handle",
//removable: "eth-ip-vars-prog-btn-del",
removable: false,
addButton: false,
addItem: function (container, i, opts){
container.addClass('eth-ip-vars-prog-item');
var titleRow = $('<div/>', {class:"eth-ip-vars-prog-title"}).appendTo(container);
var handle = $('<i class="fa fa-bars eth-ip-vars-prog-handle"/>').appendTo(titleRow);
var chevron = $('<i class="fa fa-angle-down eth-ip-vars-prog-chevron"/>').appendTo(titleRow);
var progName;
if(opts.isGlobal){
progName = $('<span style=""/>').text(labelGlobal).appendTo(titleRow);
} else {
progName = $('<input/>',{type: "text", placeholder: labelProgram, class: "eth-ip-vars-prog-name"}).appendTo(titleRow);
progName.val(opts.name);
}
var buttonGroup = $('<div/>', {style:"float: right"}).appendTo(titleRow);
var buttonAddVar = $('<a href="#" style="margin: auto 4px" class="editor-button editor-button-small"><i class="fa fa-plus"></i> ' + labelAdd + '</a>').appendTo(buttonGroup);
var buttonDelProg = $('<a href="#" style="margin: auto 4px" class="editor-button editor-button-small eth-ip-vars-prog-btn-del"><i class="fa fa-remove"></i></a>').appendTo(buttonGroup);
if(opts.isGlobal){
buttonDelProg.hide();
}
//--
var contentRow = $('<div/>', {class:"eth-ip-vars-var-container"}).appendTo(container);
var ol = $('<ol>', {class:"eth-ip-vars-var-list"}).appendTo(contentRow).editableList({
addButton: false,
height: 'auto',
connectWith: '.eth-ip-vars-var-list',
removable: true,
sortable: true,
addItem: function (container, i, opts) {
container.addClass('eth-ip-vars-var-item');
var varName = $('<input/>', {type:"text", placeholder: labelTag, class:"eth-ip-vars-var-name"}).appendTo(container);
var varType = $('<select/>', {class: "eth-ip-vars-var-type"}).appendTo(container);
for (var i in dataTypes) {
$('<option></option>').val(dataTypes[i]).text(dataTypes[i]).appendTo(varType);
}
varName.val(opts.name);
varType.val(opts.data ? opts.data.type : undefined);
}
});
//--
buttonAddVar.click(function (evt) {
ol.editableList('addItem', {});
evt.stopPropagation();
evt.preventDefault();
});
buttonDelProg.click(function (evt) {
progContainer.editableList('removeItem', container.data('data'));
evt.stopPropagation();
evt.preventDefault();
});
chevron.click(titleToggle(contentRow, chevron));
Object.keys(opts.vars || {}).forEach(function (elm) {
ol.editableList('addItem', {name: elm, data: opts.vars[elm]});
});
}
});
Object.keys(self.vartable).forEach(function (elm) {
progContainer.editableList('addItem', {name: elm, isGlobal: elm === '', vars: self.vartable[elm]});
})
// ----------------------------------------
$("#node-config-input-cycletime").spinner({
min: 50
});
},
oneditsave: function() {
var node = this;
node.vartable = generateVarTable();
}
});
})();
</script>
<!-- ######################################################################################## -->
<script type="text/x-red" data-template-name="eth-ip in">
<div class="form-row">
<label for="node-input-endpoint"><i class="fa fa-bolt"></i> <span data-i18n="ethip.in.label.endpoint"></span></label>
<input type="text" id="node-input-endpoint" data-i18n="[placeholder]ethip.in.label.endpoint">
</div>
<div class="form-row">
<label for="node-input-mode"><i class="fa fa-sliders"></i> <span data-i18n="ethip.in.label.mode"></span></label>
<select type="text" id="node-input-mode">
<option value="single" data-i18n="ethip.in.mode.single"></option>
<option value="all-split" data-i18n="ethip.in.mode.all-split"></option>
<option value="all" data-i18n="ethip.in.mode.all"></option>
</select>
</div>
<div class="form-row ethip-input-var-row">
<label for="node-input-program"><i class="fa fa-random"></i> <span data-i18n="ethip.in.label.program"></span></label>
<select type="text" id="node-input-program">
</select>
</div>
<div class="form-row ethip-input-var-row">
<label for="node-input-variable"><i class="fa fa-random"></i> <span data-i18n="ethip.in.label.variable"></span></label>
<select type="text" id="node-input-variable">
</select>
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="ethip.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]ethip.label.name">
</div>
</script>
<script type="text/x-red" data-help-name="eth-ip in">
<p>Reads data from a PLC</p>
<p>This node was created as part of the <a href="https://st-one.io" target="_blank">ST-One</a> project</p>
<p>All data is read cyclically from the PLC as configured in the <i>ethip endpoint</i>, but there are three modes of making it available in a flow:</p>
<ul>
<li><b>Single tag:</b> A single tag can be selected from the configured tags, and a message is sent every time it changes. <code>msg.payload</code> contains the tag's value and <code>msg.topic</code> has the tag's name.</li>
<li><b>All tags, one per message:</b> Like the <i>Single tag</i> mode, but for all tags configured.
<li><b>All tags:</b> In this mode, <code>msg.payload</code> contains an object with all configured tags and their values. A message is sent if at least one of the variables changes its value.</li>
</ul>
</script>
<script type="text/javascript">
RED.nodes.registerType('eth-ip in', {
category: 'plc',
defaults: {
endpoint: {
value: "",
type: "eth-ip endpoint",
required: true
},
mode: {
value: "single"
},
variable: {
value: "",
validate: function (value) {
return this.mode == "single" ? !!value : true;
}
},
program: {
value: ""
},
name: {
value: ""
}
},
color: "#D19BA1",
inputs: 0,
outputs: 1,
icon: "serial.png",
label: function() {
if (this.name) return this.name;
var endpointNode = RED.nodes.node(this.endpoint);
if (endpointNode) {
if (this.mode != 'single') {
return endpointNode.label();
}
if (this.program) {
return this.program + " / " + this.variable;
}
return this.variable || endpointNode.label();
}
return this._("ethip.in.label.name");
},
labelStyle: function() {
return this.name ? "node_label_italic" : "";
},
oneditprepare: function () {
var self = this;
var progList = $('#node-input-program');
var varList = $('#node-input-variable');
var modeList = $('#node-input-mode');
var endpointList = $("#node-input-endpoint");
var varDivs = $('.ethip-input-var-row');
var vars = {};
function cleanupLists(cleanProg) {
$('#node-input-variable option').remove();
varList.append($('<option/>', {
disabled: "disabled",
selected: "selected",
style: "display:none;",
text: self._("ethip.in.label.variable-select"),
value: ""
}));
if (!cleanProg) return;
$('#node-input-program option').remove();
}
function updateProgList(endpointId) {
//cleanup selects
cleanupLists(true);
var endpointNode = RED.nodes.node(endpointId);
if (!endpointNode) return;
vars = endpointNode.vartable || {};
Object.keys(vars).forEach(function (elm) {
var txt = elm === "" ? self._("ethip.out.label.global") : elm;
progList.append($('<option/>', {
text: txt,
value: elm
}));
if (elm === self.program) {
progList.val(self.program);
updateVarList(elm);
}
});
}
function updateVarList(progName) {
cleanupLists(false);
if(!vars[progName]) return;
Object.keys(vars[progName]).forEach(function (elm) {
varList.append($('<option/>', {
value: elm,
text: elm
}));
if (elm == self.variable) {
varList.val(self.variable);
}
});
}
progList.change(function () {
updateVarList(progList.val());
});
endpointList.change(function () {
updateProgList(endpointList.val());
});
updateProgList(self.endpoint);
modeList.change(function () {
if (modeList.val() == "single") {
varDivs.show();
} else {
varDivs.hide();
}
});
modeList.change();
},
oneditsave: function() {
var endpointNode = RED.nodes.node($("#node-input-endpoint").val());
var progValue = $('#node-input-program').val();
var varValue = $('#node-input-variable').val();
if(!endpointNode) return;
//validate: cleanup fields if they don't exist on the endpoint
if(!endpointNode.vartable[progValue]){
this.program = "";
this.variable = "";
} else if (!endpointNode.vartable[progValue][varValue]) {
this.variable = "";
}
}
});
</script>
<!-- ######################################################################################## -->
<script type="text/x-red" data-template-name="eth-ip out">
<div class="form-row">
<label for="node-input-endpoint"><i class="fa fa-bolt"></i> <span data-i18n="ethip.out.label.endpoint"></span></label>
<input type="text" id="node-input-endpoint" data-i18n="[placeholder]ethip.out.label.endpoint">
</div>
<div class="form-row">
<label for="node-input-program"><i class="fa fa-random"></i> <span data-i18n="ethip.out.label.program"></span></label>
<select type="text" id="node-input-program">
</select>
</div>
<div class="form-row">
<label for="node-input-variable"><i class="fa fa-random"></i> <span data-i18n="ethip.out.label.variable"></span></label>
<select type="text" id="node-input-variable">
</select>
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="ethip.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]ethip.label.name">
</div>
<div class="form-tips"><span data-i18n="[html]ethip.out.disclaimer"></span></div>
</script>
<script type="text/x-red" data-help-name="eth-ip out">
<p>Writes <code>msg.payload</code> to a selected PLC tag</p>
<p>This node was created as part of the <a href="https://st-one.io" target="_blank">ST-One</a> project</p>
<!--<p>The tag name can be configured in the node and takes precedence. If left blank it should be set by <code>msg.variable</code> on the incoming message.</p>-->
<p><b>Warning:</b> Caution when writing data to production PLCs!</p>
</script>
<script type="text/javascript">
RED.nodes.registerType('eth-ip out', {
category: 'plc',
defaults: {
endpoint: {
value: "",
type: "eth-ip endpoint",
required: true
},
variable: {
value: "",
required: true
},
program: {
value: ""
},
name: {
value: ""
}
},
color: "#D19BA1",
inputs: 1,
outputs: 0,
icon: "serial.png",
align: 'right',
label: function() {
if (this.name) return this.name;
var endpointNode = RED.nodes.node(this.endpoint);
if (endpointNode) {
if (this.program) {
return this.program + " / " + this.variable;
}
return this.variable || endpointNode.label();
}
return this._("ethip.out.label.name");
},
labelStyle: function() {
return this.name ? "node_label_italic" : "";
},
oneditprepare: function() {
var self = this;
var progList = $('#node-input-program');
var varList = $('#node-input-variable');
var endpointList = $("#node-input-endpoint");
var vars = {};
function cleanupLists(cleanProg){
$('#node-input-variable option').remove();
varList.append($('<option/>', {
disabled: "disabled",
selected: "selected",
style: "display:none;",
text: self._("ethip.out.label.variable-select"),
value: ""
}));
if(!cleanProg) return;
$('#node-input-program option').remove();
}
function updateProgList(endpointId) {
//cleanup selects
cleanupLists(true);
var endpointNode = RED.nodes.node(endpointId);
if (!endpointNode) return;
vars = endpointNode.vartable || {};
Object.keys(vars).forEach(function (elm) {
var txt = elm === "" ? self._("ethip.out.label.global") : elm;
progList.append($('<option/>', {
text: txt,
value: elm
}));
if(elm === self.program){
progList.val(self.program);
updateVarList(elm);
}
});
}
function updateVarList(progName) {
cleanupLists(false);
if(!vars[progName]) return;
Object.keys(vars[progName]).forEach(function (elm){
varList.append($('<option/>', {
value: elm,
text: elm
}));
if(elm == self.variable) {
varList.val(self.variable);
}
});
}
progList.change(function(){
updateVarList(progList.val());
});
endpointList.change(function() {
updateProgList(endpointList.val());
});
updateProgList(self.endpoint);
},
oneditsave: function () {
var endpointNode = RED.nodes.node($("#node-input-endpoint").val());
var progValue = $('#node-input-program').val();
var varValue = $('#node-input-variable').val();
if (!endpointNode) return;
//validate: cleanup fields if they don't exist on the endpoint
if (!endpointNode.vartable[progValue]) {
this.program = "";
this.variable = "";
} else if (!endpointNode.vartable[progValue][varValue]) {
this.variable = "";
}
}
});
</script>