node-red-contrib-iiot-opcua
Version:
An Industrial IoT OPC UA toolbox contribution package for Node-RED based on node-opcua.
271 lines (238 loc) • 13.1 kB
HTML
<!--
The BSD 3-Clause License
Copyright 2022 - DATATRONiQ GmbH (https://datatroniq.com)
Copyright (c) 2018-2022 Klaus Landsdorf (http://node-red.plus/)
Copyright 2015,2016 - Mika Karaila, Valmet Automation Inc. (node-red-contrib-opcua)
All rights reserved.
node-red-contrib-iiot-opcua
--> <script type='text/javascript'> RED.nodes.registerType('OPCUA-IIoT-Crawler', {
category: 'IIoT',
color: '#ABCDEF',
defaults: {
connector: {type: "OPCUA-IIoT-Connector", required: true},
name: {value: ""},
justValue: {value: true},
singleResult: {value: false},
showStatusActivities: {value: false},
showErrors: {value: false},
activateUnsetFilter: {value: false},
activateFilters: {value: false},
negateFilter: {value: false},
filters: {value: []},
delayPerMessage: {value: 0.2},
timeout: {value: 30},
},
inputs: 1,
outputs: 1,
align: "left",
icon: "icon.png",
label: function () {
return this.name || "Crawler";
},
labelStyle: function () {
return this.item ? "node_label_italic" : "";
},
oneditprepare: function () {
let node = this
let cacheItemCount = 0
let tabs = RED.tabs.create({
id: "node-input-crawler-tabs",
onchange: function (tab) {
$("#node-input-crawler-tabs-content").children().hide()
$("#" + tab.id).show()
}
})
tabs.addTab({
id: "opcuaiiot-crawler-tab-settings",
label: this._("opcua-iiot-contrib.tabs-label.settings")
})
tabs.addTab({
id: "opcuaiiot-crawler-tab-filter",
label: this._("opcua-iiot-contrib.tabs-label.filter")
})
// Filter Management
if(node.filters && node.filters.length > 0) {
cacheItemCount = node.filters.length
node.filters.forEach(function(element, index, array) {
generateFilterEntry(element, index);
})
}
function generateFilterEntry(filter, id) {
let container = $('<li/>', {
style:
'background: #fefefe; margin:0; padding:8px 0px; border-bottom: 1px solid #ccc;'
})
let row = $('<div class="row" id="row' + id + '" />').appendTo(container)
$('<i style="color: #eee; cursor: move;" class="node-input-addressSpaceItems-handle fa fa-bars"></i>').appendTo(row)
let filterNameField = $('<input/>', {
id: "node-input-crawler-filter-name" + id,
class: 'opcuaFilterName',
type: "text",
style: "margin-left:5px;width:240px;",
placeholder: 'nodeClass'
}).appendTo(row)
let filterValueField = $('<input/>', {
id: "node-input-crawler-filter-value" + id,
class: 'opcuaFilterValue',
type: "text",
style: "margin: 0 auto;width:45%;min-width:60px;margin-left:5px",
placeholder: 'Object*'
}).appendTo(row)
filterNameField.val(filter.name)
filterValueField.val(filter.value)
let finalspan = $('<span/>', {style: 'float: right;margin-right: 10px;'}).appendTo(row)
let lookupItemButton = $('<a/>', {
href: '#',
id: 'node-button-crawler-filter-types' + id,
class: 'editor-button editor-button-small listButton',
style: 'margin-top: 7px; margin-left: 5px;'
}).appendTo(finalspan)
lookupItemButton.click(function () {
let filterLookupButton = $('#node-button-crawler-filter-types' + id)
let filterNameLookupField = $('#row' + id + ' #node-input-crawler-filter-name' + id)
filterLookupButton.addClass('disabled')
$.getJSON('/opcuaIIoT/list/FilterTypes', function (data) {
filterLookupButton.removeClass('disabled')
node.lookupItems = []
$.each(data, function (i, entry) {
node.lookupItems.push({name: entry.name, label: entry.label})
})
filterNameLookupField.autocomplete({
source: node.lookupItems,
minLength: 0,
focus: function (event, ui) {
filterNameLookupField.val(ui.item.name)
return false;
},
select: function (event, ui) {
filterNameLookupField.val(ui.item.name)
return false;
},
close: function (event, ui) {
filterNameLookupField.autocomplete("destroy")
}
}).autocomplete("search", "")
})
})
$('<i/>', {class: 'fa fa-search'}).appendTo(lookupItemButton)
let removeItemButton = $('<a/>', {
href: '#',
id: 'node-button-crawler-filter-remove' + id,
class: 'editor-button editor-button-small listButton',
style: 'margin-top: 7px; margin-left: 5px;'
}).appendTo(finalspan)
$('<i/>', {class: 'fa fa-remove'}).appendTo(removeItemButton)
removeItemButton.click(function () {
container.fadeOut(300, function () {
$(this).remove()
})
})
$('#node-input-crawler-filter-container').append(container)
}
$("#node-input-crawler-filter-container").sortable({
axis: "y",
handle: ".node-input-crawler-filter-handle",
cursor: "move"
});
$("#node-input-crawler-filter-container .node-input-crawler-filter-handle").disableSelection()
$("#node-input-crawler-filter-add").click(function () {
if(!cacheItemCount || cacheItemCount < 0) {
cacheItemCount = 0
}
generateFilterEntry({ name: '', value: '' }, cacheItemCount++) // length is every time one more than index
let crawlerContainerDiv = $("#node-input-crawler-filter-container-div")
crawlerContainerDiv.scrollTop(crawlerContainerDiv.get(0).scrollHeight)
})
function switchDialogResize() {
switchFilterDialogResize()
}
// dialog Filter handling
function switchFilterDialogResize() {
let rows = $("#dialog-form>div:not(.node-input-crawler-filter-container-row)")
let height = $("#dialog-form").height()
rows.each(function (index, row) {
height -= row.outerHeight(true)
})
let editorRow = $("#dialog-form>div.node-input-crawler-filter-container-row")
height -= (parseInt(editorRow.css("marginTop")) + parseInt(editorRow.css("marginBottom")))
$("#node-input-crawler-filter-container-div").css("height", height + "px")
}
let dialogHandle = $("#dialog")
dialogHandle.on("dialogresize", switchDialogResize)
dialogHandle.on("dialogopen", function (ev) {
let size = dialogHandle.dialog('option', 'sizeCache-switch')
if (size) {
dialogHandle.dialog('option', 'width', size.width)
dialogHandle.dialog('option', 'height', size.height)
switchDialogResize()
} else {
setTimeout(switchDialogResize, 10)
}
})
dialogHandle.on("dialogclose", function (ev, ui) {
dialogHandle.off("dialogresize", switchDialogResize)
})
},
oneditsave: function () {
let node = this
let cacheFilters = $("#node-input-crawler-filter-container").children()
node.filters = []
cacheFilters.each(function () {
node.filters.push({
name: $(this).find('.opcuaFilterName').val(),
value: $(this).find('.opcuaFilterValue').val(),
})
})
}
}); </script> <script type='text/x-red' data-template-name='OPCUA-IIoT-Crawler'>
<div class='form-row'> <ul style='background:#fff;min-width:600px;margin-bottom:20px' id='node-input-crawler-tabs'>
</ul> </div> <div id='node-input-crawler-tabs-content' style='min-height:170px'> <div
id='opcuaiiot-crawler-tab-settings' style='display:none'> <div class='form-row' style='min-width:640px'> <label
for='node-input-connector'><i class='icon-globe'></i> <span data-i18n='opcua-iiot-contrib.label.connector'></span>
</label> <input type='text' id='node-input-connector'> </div> <div class='form-row'> <label for='node-input-name'><i
class='fa fa-tag'></i> <span data-i18n='node-red:common.label.name'></span></label> <input type='text'
id='node-input-name' placeholder='Name'> </div> <div class='form-row'> <label for='node-input-delayPerMessage'><i
class='icon-time'></i> <span data-i18n='opcua-iiot-contrib.label.delayPerMessage'></span></label> <input type='text'
id='node-input-delayPerMessage' placeholder='0.2' style='max-width:80px'> sec. </div> <div class='form-row'> <label
for='node-input-timeout'><i class='icon-time'></i> <span data-i18n='opcua-iiot-contrib.label.timeout'></span></label>
<input type='text' id='node-input-timeout' placeholder='30' style='max-width:80px'> sec. </div> <hr> <div
class='form-row'> <label style='min-width:160px' for='node-input-justValue'><i class='fa fa-simplybuilt'></i> <span
data-i18n='opcua-iiot-contrib.label.justValue'></span></label> <input type='checkbox' id='node-input-justValue'
style='max-width:30px'> </div> <div class='form-row'> <label style='min-width:160px' for='node-input-singleResult'><i
class='fa fa-simplybuilt'></i> <span data-i18n='opcua-iiot-contrib.label.singleResult'></span></label> <input
type='checkbox' id='node-input-singleResult' style='max-width:30px'> </div> <hr> <div class='form-row'> <label
style='min-width:160px' for='node-input-showStatusActivities'><i class='fa fa-bolt'></i> <span
data-i18n='opcua-iiot-contrib.label.showActivities'></span></label> <input type='checkbox'
id='node-input-showStatusActivities' style='max-width:30px'> </div> <div class='form-row'> <label
style='min-width:160px' for='node-input-showErrors'><i class='fa fa-exclamation-circle'></i> <span
data-i18n='opcua-iiot-contrib.label.showErrors'></span></label> <input type='checkbox' id='node-input-showErrors'
style='max-width:30px'> </div> </div> <div id='opcuaiiot-crawler-tab-filter' style='display:none'> <h4><span
data-i18n='opcua-iiot-contrib.label.filters'></span></h4> <div class='form-row'> <label style='min-width:160px'
for='node-input-activateUnsetFilter'><i class='fa fa-filter'></i> <span
data-i18n='opcua-iiot-contrib.label.activateUnsetFilter'></span></label> <input type='checkbox'
id='node-input-activateUnsetFilter' style='max-width:30px'> </div> <div class='form-row'> <label
style='min-width:160px' for='node-input-activateFilters'><i class='fa fa-check-circle'></i> <span
data-i18n='opcua-iiot-contrib.label.activateFilters'></span></label> <input type='checkbox'
id='node-input-activateFilters' style='max-width:30px'> </div> <div class='form-row'> <label style='min-width:160px'
for='node-input-negateFilter'><i class='fa fa-minus-circle'></i> <span
data-i18n='opcua-iiot-contrib.label.negateFilter'></span></label> <input type='checkbox' id='node-input-negateFilter'
style='max-width:30px'> </div> <div class='form-row node-input-crawler-filter-container-row' style='margin-bottom:0'>
<div id='node-input-crawler-filter-container-div'
style='box-sizing:border-box;border-radius:5px;height:480px;padding:5px;border:1px solid #ccc;overflow-y:scroll'> <ol
id='node-input-crawler-filter-container' style='list-style-type:none;margin:0'></ol> </div> </div> <div
class='form-row'> <a href='#' class='editor-button editor-button-small' id='node-input-crawler-filter-add'
style='margin-top:4px'><i class='fa fa-plus'></i> <span data-i18n='opcua-iiot-contrib.label.addButton'></span></a>
</div> </div> </div> <!-- TODO: ReferenceTypeIds List to add or remove reference types tp browse for --> </script>
<script type='text/x-red' data-help-name='OPCUA-IIoT-Crawler'> <h2>OPC UA IIoT Crawler</h2> <p>
Use the Crawler to browse OPC UA address spaces. It normally starts with a root folder or object by using an OPC UA Node-Id like i=85 (root->Objects), but that is not a good practice. Set up folders without crawling the server object and its types in depth.
</p> <h3>Configuration</h3> <p>
The filter tab inside the configuration section is used to filter crawling by properties. You can set your own or use some of the listed properties.
</p><p> </p><h3>Examples</h3> <a href='https://github.com/biancode/node-red-contrib-iiot-opcua/blob/master/examples/'
target='_blank'>GitHub Crawler example flow</a> <p>
You'll find the examples under Node-RED menu > Import > Examples > iiot opcua</p> <h3>Input</h3> <p>
The input has to receive an addressSpaceItems (Array of objects) to browse for. A datatype or name is not required here - it just requires the nodeId in the addressSpaceItems objects here.
</p> <p>Filter: </p><ul> <li>name => property name to search inside result per item </li><li>
value => regex string to match content </li></ul> <p></p> <h3>Output</h3> <p>
The output returns either a structured JSON object with the requested data or the error messages from node-opcua.</p>
<strong>Name</strong> <p>Name in the flow of Node-RED.</p> <p>Set showErrors to get errors from node-opcua on browse.
</p> </script>