node-red-contrib-iiot-opcua
Version:
An Industrial IoT OPC UA toolbox contribution package for Node-RED based on node-opcua.
513 lines (448 loc) • 32.6 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-Flex-Server', {
category: 'IIoT',
color: '#ABCDEF',
defaults: {
port: {value: '55380', required: true},
endpoint: {value: ''},
acceptExternalCommands: {value: true},
maxAllowedSessionNumber: {value: ''},
maxConnectionsPerEndpoint: {value: ''},
maxAllowedSubscriptionNumber: {value: ''},
alternateHostname: {value: ''},
name: {value: ''},
showStatusActivities: {value: false},
showErrors: {value: false},
allowAnonymous: {value: true},
individualCerts: {value: false},
isAuditing: {value: false},
serverDiscovery: {value: true},
users: { value: [] },
xmlsets: { value: [] },
publicCertificateFile: {value: ''},
privateCertificateFile: {value: ''},
registerServerMethod: {value: 1},
discoveryServerEndpointUrl: {value: ''},
capabilitiesForMDNS: {value: ''},
maxNodesPerRead: {value: 1000, validate:function(v) { return ((v === '') || (RED.validators.number(v) && (v > 0) && (v <= 100000))) }},
maxNodesPerBrowse: {value: 2000, validate:function(v) { return ((v === '') || (RED.validators.number(v) && (v > 0) && (v <= 200000))) }},
delayToClose: {value: 1000, validate:function(v) { return ((v === '') || (RED.validators.number(v) && (v >= 100) && (v <= 15000))) }},
addressSpaceScript: { value: "function constructAlarmAddressSpace(server, addressSpace, eventObjects, done) {\n // server = the created node-opcua server\n // addressSpace = script placeholder\n // eventObjects = to hold event variables in memory from this script\n \n // internal global sandbox objects are \n // node = node of the flex server, \n // coreServer = core iiot server object for debug and access to nodeOPCUA,\n // and scriptObjects to hold variables and functions\n const LocalizedText = coreServer.core.nodeOPCUA.LocalizedText\n const namespace = addressSpace.getOwnNamespace()\n\n coreServer.internalDebugLog('init dynamic address space')\n node.warn('construct new address space for OPC UA')\n \n // from here - see the node-opcua docs how to build address sapces\n let tanks = namespace.addObject({\n browseName: 'Tanks',\n description: 'The Object representing some tanks',\n organizedBy: addressSpace.rootFolder.objects,\n notifierOf: addressSpace.rootFolder.objects.server\n })\n \n let oilTankLevel = namespace.addVariable({\n browseName: 'OilTankLevel',\n displayName: [\n new LocalizedText({text: 'Oil Tank Level', locale: 'en-US'}),\n new LocalizedText({text: 'Öl Tank Füllstand', locale: 'de-DE'})\n ],\n description: 'Fill level in percentage (0% to 100%) of the oil tank',\n propertyOf: tanks,\n dataType: 'Double',\n eventSourceOf: tanks\n })\n \n // ---------------------------------------------------------------------------------\n // Let's create a exclusive Limit Alarm that automatically raise itself\n // when the tank level is out of limit\n // ---------------------------------------------------------------------------------\n let exclusiveLimitAlarmType = addressSpace.findEventType('ExclusiveLimitAlarmType')\n if (nonExclusiveLimitAlarmType === null) throw new Error(\"nonExclusiveLimitAlarmType Error Message\")(exclusiveLimitAlarmType !== null)\n \n let oilTankLevelCondition = namespace.instantiateExclusiveLimitAlarm(exclusiveLimitAlarmType, {\n componentOf: tanks,\n conditionSource: oilTankLevel,\n browseName: 'OilTankLevelCondition',\n displayName: [\n new LocalizedText({text: 'Oil Tank Level Condition', locale: 'en-US'}),\n new LocalizedText({text: 'Öl Tank Füllstand Bedingung', locale: 'de-DE'})\n ],\n description: 'ExclusiveLimitAlarmType Condition',\n conditionName: 'OilLevelCondition',\n optionals: [\n 'ConfirmedState', 'Confirm' // confirm state and confirm Method\n ],\n inputNode: oilTankLevel, // the letiable that will be monitored for change\n highHighLimit: 0.9,\n highLimit: 0.8,\n lowLimit: 0.2\n })\n \n // --------------------------------------------------------------\n // Let's create a second letiable with no Exclusive alarm\n // --------------------------------------------------------------\n let gasTankLevel = namespace.addVariable({\n browseName: 'GasTankLevel',\n displayName: [\n new LocalizedText({text: 'Gas Tank Level', locale: 'en-US'}),\n new LocalizedText({text: 'Gas Tank Füllstand', locale: 'de-DE'})\n ],\n description: 'Fill level in percentage (0% to 100%) of the gas tank',\n propertyOf: tanks,\n dataType: 'Double',\n eventSourceOf: tanks\n })\n \n let nonExclusiveLimitAlarmType = addressSpace.findEventType('NonExclusiveLimitAlarmType')\n if (nonExclusiveLimitAlarmType === null) throw new Error(\"nonExclusiveLimitAlarmType Error Message\")(nonExclusiveLimitAlarmType !== null)\n \n let gasTankLevelCondition = namespace.instantiateNonExclusiveLimitAlarm(nonExclusiveLimitAlarmType, {\n componentOf: tanks,\n conditionSource: gasTankLevel,\n browseName: 'GasTankLevelCondition',\n displayName: [\n new LocalizedText({text: 'Gas Tank Level Condition', locale: 'en-US'}),\n new LocalizedText({text: 'Gas Tank Füllstand Bedingung', locale: 'de-DE'})\n ],\n description: 'NonExclusiveLimitAlarmType Condition',\n conditionName: 'GasLevelCondition',\n optionals: [\n 'ConfirmedState', 'Confirm' // confirm state and confirm Method\n ],\n inputNode: gasTankLevel, // the letiable that will be monitored for change\n highHighLimit: 0.9,\n highLimit: 0.8,\n lowLimit: 0.2\n })\n \n // variable with value\n if(scriptObjects.testReadWrite === undefined || scriptObjects.testReadWrite === null) {\n scriptObjects.testReadWrite = 1000.0\n }\n \n let myVariables = namespace.addObject({\n browseName: 'MyVariables',\n description: 'The Object representing some variables',\n organizedBy: addressSpace.rootFolder.objects,\n notifierOf: addressSpace.rootFolder.objects.server\n })\n \n if(coreServer.core) {\n namespace.addVariable({\n componentOf: myVariables,\n nodeId: 'ns=1;s=TestReadWrite',\n browseName: 'TestReadWrite',\n displayName: [\n new LocalizedText({text: 'Test Read and Write', locale: 'en-US'}),\n new LocalizedText({text: 'Test Lesen Schreiben', locale: 'de-DE'})\n ],\n dataType: 'Double',\n value: {\n get: function () {\n return new coreServer.core.nodeOPCUA.Variant({\n dataType: 'Double',\n value: scriptObjects.testReadWrite\n })\n },\n set: function (variant) {\n scriptObjects.testReadWrite = parseFloat(variant.value)\n return coreServer.core.nodeOPCUA.StatusCodes.Good\n }\n }\n \n })\n \n let memoryVariable = namespace.addVariable({\n componentOf: myVariables,\n nodeId: 'ns=1;s=free_memory',\n browseName: 'FreeMemory',\n displayName: [\n new LocalizedText({text: 'Free Memory', locale: 'en-US'}),\n new LocalizedText({text: 'ungenutzer RAM', locale: 'de-DE'})\n ],\n dataType: 'Double',\n \n value: {\n get: function () {\n return new coreServer.core.nodeOPCUA.Variant({\n dataType: 'Double',\n value: coreServer.core.availableMemory()\n })\n }\n }\n })\n addressSpace.installHistoricalDataNode(memoryVariable)\n \n } else {\n coreServer.internalDebugLog('coreServer.core needed for coreServer.core.nodeOPCUA')\n }\n\n // hold event objects in memory \n eventObjects.oilTankLevel = oilTankLevel\n eventObjects.oilTankLevelCondition = oilTankLevelCondition\n \n eventObjects.gasTankLevel = gasTankLevel\n eventObjects.gasTankLevelCondition = gasTankLevelCondition\n \n done()\n}"
}
},
inputs: 1,
outputs: 1,
align: 'right',
icon: 'icon.png',
label: function () {
return this.name || 'Flex-Server'
},
labelStyle: function () {
return this.name ? 'node_label_italic' : ''
},
oneditprepare: function () {
let node = this
node.xmlsetSelect = []
let cacheItemCount = 0
let cacheItemCountSet = 0
let tabs = RED.tabs.create({
id: "node-input-server-tabs",
onchange: function (tab) {
$("#node-input-server-tabs-content").children().hide()
$("#" + tab.id).show()
}
})
tabs.addTab({
id: "opcuaiiot-server-tab-settings",
label: this._("opcua-iiot-contrib.tabs-label.settings")
})
tabs.addTab({
id: "opcuaiiot-server-tab-limits",
label: this._("opcua-iiot-contrib.tabs-label.limits")
})
tabs.addTab({
id: "opcuaiiot-server-tab-security",
label: this._("opcua-iiot-contrib.tabs-label.security")
})
tabs.addTab({
id: "opcuaiiot-server-tab-users",
label: this._("opcua-iiot-contrib.tabs-label.users")
})
tabs.addTab({
id: "opcuaiiot-server-tab-ass",
label: this._("opcua-iiot-contrib.tabs-label.ass")
})
tabs.addTab({
id: "opcuaiiot-server-tab-discovery",
label: this._("opcua-iiot-contrib.tabs-label.discovery")
})
// User Management
if(node.users && node.users.length > 0) {
cacheItemCount = node.users.length
node.users.forEach(function(element, index, array) {
generateUserEntry(element, index);
})
}
// XML-Set Management
if(node.xmlsets && node.xmlsets.length > 0) {
cacheItemCountSet = node.xmlsets.length
node.xmlsets.forEach(function(element, index, array) {
generateXMLSetEntry(element, index);
})
}
function generateUserEntry(user, id) {
let container = $('<li/>', {style:
"background: #fefefe; margin:0; padding:8px 0px; border-bottom: 1px solid #ccc;"})
let row = $('<div id="row' + id + '"/>').appendTo(container)
$('<i style="color: #eee; cursor: move;" class="node-input-server-users-handle fa fa-bars"></i>').appendTo(row)
let userField = $('<input/>', {
id: "node-input-server-users-name" + id,
class: 'opcuaUserName',
type: "text",
style: "margin-left:5px;width:160px;",
placeholder: 'name'
}).appendTo(row)
let passwordField = $('<input/>', {
id: "node-input-server-users-password" + id,
class: 'opcuaUserPassword',
type: "password",
style: "margin: 0 auto;width:55%;min-width:60px;margin-left:5px",
placeholder: 'password'
}).appendTo(row)
userField.val(user.name)
passwordField.val(user.password)
let finalspan = $('<span/>', {style: "float: right;margin-right: 10px;"}).appendTo(row)
let removeUserButton = $('<a/>', {
href: "#",
id: 'node-button-user-remove' + id,
class: "editor-button editor-button-small",
style: "margin-top: 7px; margin-left: 5px;"
}).appendTo(finalspan)
$('<i/>', {class: "fa fa-remove"}).appendTo(removeUserButton)
removeUserButton.click(function () {
container.css({"background": "#fee"})
container.fadeOut(300, function () {
$(this).remove()
});
});
$("#node-input-server-users-container").append(container)
}
function generateXMLSetEntry(xmlset, id) {
let container = $('<li/>', {style:
"background: #fefefe; margin:0; padding:8px 0px; border-bottom: 1px solid #ccc;"})
let row = $('<div id="xmlsetRow' + id + '"/>').appendTo(container)
$('<i style="color: #eee; cursor: move;" class="node-input-server-xmlsets-handle fa fa-bars"></i>').appendTo(row)
let nameField = $('<input/>', {
id: "node-input-server-xmlsets-name" + id,
type: "text",
class: 'xmlsetName',
style: "margin-left:5px;width:75px;",
placeholder: 'name'
}).appendTo(row)
let pathField = $('<input/>', {
id: "node-input-server-xmlsets-path" + id,
class: 'xmlsetPath',
type: "text",
style: "margin: 0 auto;width:68%;min-width:60px;margin-left:5px",
placeholder: 'public/vendor/opc-foundation/xml/Opc.ISA95.NodeSet2.xml'
}).appendTo(row)
nameField.val(xmlset.name)
pathField.val(xmlset.path)
let finalspan = $('<span/>', {style: "float: right;margin-right: 10px;"}).appendTo(row)
let lookupItemButton = $('<a/>', {
href: '#',
id: 'node-button-xmlsets-lookup' + id,
class: 'editor-button editor-button-small',
style: 'margin-top: 7px; margin-left: 5px;'
}).appendTo(finalspan)
lookupItemButton.click(function () {
let xmlsetLookupButton = $('#node-button-xmlsets-lookup' + id)
let xmlsetLookupField = $('#xmlsetRow' + id + ' #node-input-server-xmlsets-path' + id)
xmlsetLookupButton.addClass('disabled')
$.getJSON('/opcuaIIoT/xmlsets/public', function (data) {
xmlsetLookupButton.removeClass('disabled')
node.xmlsetSelect = []
$.each(data, function (i, filename) {
node.xmlsetSelect.push(filename)
})
xmlsetLookupField.autocomplete({
source: node.xmlsetSelect,
minLength: 0,
close: function (event, ui) {
xmlsetLookupField.autocomplete('destroy')
}
}).autocomplete('search', '')
})
})
$('<i/>', {class: 'fa fa-search'}).appendTo(lookupItemButton)
let removeXMLSetButton = $('<a/>', {
href: "#",
id: 'node-button-xmlset-remove' + id,
class: "editor-button editor-button-small",
style: "margin-top:7px;margin-left:5px;"
}).appendTo(finalspan)
$('<i/>', {class: "fa fa-remove"}).appendTo(removeXMLSetButton)
removeXMLSetButton.click(function () {
container.css({"background": "#fee"})
container.fadeOut(300, function () {
$(this).remove()
});
});
$("#node-input-server-xmlsets-container").append(container)
}
$("#node-input-server-users-container").sortable({
axis: "y",
handle: ".node-input-server-users-handle",
cursor: "move"
});
$("#node-input-server-xmlsets-container").sortable({
axis: "y",
handle: ".node-input-server-xmlsets-handle",
cursor: "move"
});
$("#node-input-server-users-container .node-input-server-users-handle").disableSelection()
$("#node-input-server-xmlsets-container .node-input-server-xmlsets-handle").disableSelection()
$("#node-input-server-users-add").click(function () {
if(!cacheItemCount || cacheItemCount < 0) {
cacheItemCount = 0
}
generateUserEntry({ name: '', password: '' }, cacheItemCount++) // length is every time one more than index
$("#node-input-server-users-container-div").scrollTop($("#node-input-server-users-container-div").get(0).scrollHeight)
})
$("#node-input-server-xmlsets-add").click(function () {
if(!cacheItemCountSet || cacheItemCountSet < 0) {
cacheItemCountSet = 0
}
generateXMLSetEntry({ name: '', path: '' }, cacheItemCountSet++) // length is every time one more than index
$("#node-input-server-xmlsets-container-div").scrollTop($("#node-input-server-xmlsets-container-div").get(0).scrollHeight)
})
function switchDialogResize() {
switchUserDialogResize()
switchXMLSetDialogSetResize()
}
// dialog User handling
function switchUserDialogResize() {
let rows = $("#dialog-form>div:not(.node-input-server-users-container-row)")
let height = $("#dialog-form").height()
rows.each(function (index, row) {
height -= row.outerHeight(true)
})
let editorRow = $("#dialog-form>div.node-input-server-users-container-row")
height -= (parseInt(editorRow.css("marginTop")) + parseInt(editorRow.css("marginBottom")))
$("#node-input-server-users-container-div").css("height", height + "px")
}
// dialog XML-Set handling
function switchXMLSetDialogSetResize() {
let rows = $("#dialogSet-form>div:not(.node-input-server-xmlsets-container-row)")
let height = $("#dialogSet-form").height()
rows.each(function (index, row) {
height -= row.outerHeight(true)
})
let editorRow = $("#dialogSet-form>div.node-input-server-xmlsets-container-row")
height -= (parseInt(editorRow.css("marginTop")) + parseInt(editorRow.css("marginBottom")))
$("#node-input-server-xmlsets-container-div").css("height", height + "px")
}
$("#dialog").on("dialogresize", switchDialogResize)
$("#dialog").on("dialogopen", function (ev) {
let size = $("#dialog").dialog('option', 'sizeCache-switch')
if (size) {
$("#dialog").dialog('option', 'width', size.width)
$("#dialog").dialog('option', 'height', size.height)
switchDialogResize()
} else {
setTimeout(switchDialogResize, 10)
}
})
$("#dialog").on("dialogclose", function (ev, ui) {
$("#dialog").off("dialogresize", switchDialogResize)
})
// address space script
node.editorAddressSpaceScript = RED.editor.createEditor({
id: 'node-input-func-editor-addressSpaceScript',
mode: 'ace/mode/javascript',
value: $("#node-input-addressSpaceScript").val(),
globals: {
msg:true,
context:true,
RED: true,
util: true,
flow: true,
global: true,
console: true,
Buffer: true,
setTimeout: true,
clearTimeout: true,
setInterval: true,
clearInterval: true,
draggable: true
}
});
$( function() {
$( "#node-input-func-editor-addressSpaceScript" ).resizable();
} );
let element = document.getElementById('node-input-func-editor-addressSpaceScript');
element.addEventListener("mouseup", resizeScript, false);
function resizeScript () {
node.editorAddressSpaceScript.resize()
}
// Certificate Management
try {
let certsCheckbox = $("#node-input-individualCerts");
let configCertFields = $("#node-config-certFiles");
if (node.individualCerts) {
certsCheckbox.prop('checked', true);
configCertFields.show();
} else {
certsCheckbox.prop('checked', false);
configCertFields.hide();
}
certsCheckbox.change(function () {
if ($(this).is(":checked")) {
configCertFields.show();
} else {
configCertFields.hide();
$('#node-config-input-publicCertificateFile').val('');
$('#node-config-input-privateKeyFile').val('');
}
});
} catch (err) {
this.error(err);
}
},
oneditsave: function () {
let node = this
let cacheUsers = $("#node-input-server-users-container").children()
node.users = []
cacheUsers.each(function () {
node.users.push({
name: $(this).find('.opcuaUserName').val(),
password: $(this).find('.opcuaUserPassword').val(),
})
})
let cacheXMLSets = $('#node-input-server-xmlsets-container').children()
node.xmlsets = []
cacheXMLSets.each(function () {
node.xmlsets.push({
name: $(this).find('.xmlsetName').val(),
path: $(this).find('.xmlsetPath').val(),
})
})
$("#node-input-addressSpaceScript").val(this.editorAddressSpaceScript.getValue());
this.editorAddressSpaceScript.destroy();
delete this.editorAddressSpaceScript;
// console.log('well done editsave ...')
}
}) </script> <script type='text/x-red' data-template-name='OPCUA-IIoT-Flex-Server'>
<div class='form-row'> <ul style='background:#fff;min-width:600px;margin-bottom:20px' id='node-input-server-tabs'></ul>
</div> <div id='node-input-server-tabs-content' style='min-height:170px'> <div id='opcuaiiot-server-tab-settings'
style='display:none'> <div class='form-row'> <label for='node-input-port'><i class='fa fa-fort-awesom'></i> <span
data-i18n='opcua-iiot-contrib.label.port'></span></label>
<!-- dynamic Ports from 49152 to 65535 (c000hex bis FFFFhex) --> <input type='text' id='node-input-port'
placeholder='Ports 49152 bis 65535 (c000hex bis FFFFhex)'> </div> <div class='form-row'> <label
for='node-input-endpoint'><i class='icon-tasks'></i> <span data-i18n='opcua-iiot-contrib.label.endpoint'></span></label>
<input type='text' id='node-input-endpoint' placeholder='UA/NodeREDFlexIIoTServer'> </div> <div class='form-row'>
<label for='node-input-alternateHostname'><i class='fa fa-server'></i> <span
data-i18n='opcua-iiot-contrib.label.alternateHostname'></span></label> <input type='text'
id='node-input-alternateHostname' placeholder=''> </div> <hr> <div class='form-row'> <label
for='node-input-delayToClose'><i class='icon-time'></i> <span data-i18n='opcua-iiot-contrib.label.delayToClose'></span>
</label> <input type='text' id='node-input-delayToClose' placeholder='1000' style='width:80px'> </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=''> </div> <div class='form-row'> <label
style='min-width:160px' for='node-input-isAuditing'><i class='fa fa-th'></i> <span
data-i18n='opcua-iiot-contrib.label.isAuditing'></span></label> <input type='checkbox' id='node-input-isAuditing'
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-server-tab-limits' style='display:none'> <div class='form-row'>
<label for='node-input-maxAllowedSessionNumber'><i class='icon-list'></i> <span
data-i18n='opcua-iiot-contrib.label.maxAllowedSessionNumber'></span></label> <input type='text'
id='node-input-maxAllowedSessionNumber' placeholder='10' style='width:80px'> </div> <div class='form-row'> <label
for='node-input-maxConnectionsPerEndpoint'><i class='icon-list'></i> <span
data-i18n='opcua-iiot-contrib.label.maxConnectionsPerEndpoint'></span></label> <input type='text'
id='node-input-maxConnectionsPerEndpoint' placeholder='10' style='width:80px'> </div> <div class='form-row'> <label
for='node-input-maxAllowedSubscriptionNumber'><i class='icon-list'></i> <span
data-i18n='opcua-iiot-contrib.label.maxAllowedSubscriptionNumber'></span></label> <input type='text'
id='node-input-maxAllowedSubscriptionNumber' placeholder='50' style='width:80px'> </div> <div class='form-row'> <label
for='node-input-maxNodesPerRead'><i class='icon-list'></i> <span data-i18n='opcua-iiot-contrib.label.maxNodesPerRead'>
</span></label> <input type='text' id='node-input-maxNodesPerRead' placeholder='1000' style='width:80px'> </div> <div
class='form-row'> <label for='node-input-maxNodesPerBrowse'><i class='icon-list'></i> <span
data-i18n='opcua-iiot-contrib.label.maxNodesPerBrowse'></span></label> <input type='text'
id='node-input-maxNodesPerBrowse' placeholder='2000' style='width:80px'> </div> </div> <div
id='opcuaiiot-server-tab-security' style='display:none'> <div class='form-row'> <!-- SecurityPolicy enum via REST -->
<label for='node-input-securityPolicy' style='min-width:140px'><i class='fa fa-certificate'></i> <span
data-i18n='opcua-iiot-contrib.label.securityPolicy'></span></label> <ul style='margin-left:80px'> <li>None</li> <li>
Basic128Rsa15</li> <li>Basic256</li> <li>Basic256Sha256</li> </ul> </div> <div class='form-row'>
<!-- MessageSecurityMode enum via REST --> <label for='node-input-securityMode' style='min-width:140px'><i
class='fa fa-key'></i> <span data-i18n='opcua-iiot-contrib.label.securityMode'></span></label> <ul
style='margin-left:80px'> <li>None</li> <li>Sign</li> <li>Sign&Encrypt</li> </ul> </div> <div class='form-row'> <label
style='min-width:140px'> <i class='fa fa-certificate'></i> <span data-i18n='opcua-iiot-contrib.label.certificateFiles'>
</span> </label> </div> <div class='form-row'> <label style='min-width:280px'> <i class='fa fa-file'></i> <span
data-i18n='opcua-iiot-contrib.label.individualCerts'></span> <input type='checkbox' id='node-input-individualCerts'
style='max-width:30px'> </label> </div> <div id='node-config-certFiles'> <hr> <div class='form-row'> <label
for='node-input-publicCertificateFile'><i class='fa fa-file'></i> <span
data-i18n='opcua-iiot-contrib.label.publicCertificateFile'></span></label> <input type='text'
id='node-input-publicCertificateFile' placeholder='./certificates/server_selfsigned_cert_2048.pem'
style='min-width:480px;width:100%'> </div> <div class='form-row'> <label for='node-input-privateCertificateFile'><i
class='fa fa-file'></i> <span data-i18n='opcua-iiot-contrib.label.privateCertificateFile'></span></label> <input
type='text' id='node-input-privateCertificateFile' placeholder='./certificates/PKI/own/private/private_key.pem'
style='min-width:480px;width:100%'> </div> <hr> </div> <div class='form-row'> <label for='node-input-allowAnonymous'
style='min-width:140px'><i class='icon-lock'></i> <span data-i18n='opcua-iiot-contrib.label.allowAnonymous'></span>
</label> <input type='checkbox' id='node-input-allowAnonymous' style='max-width:35px'> </div> </div> <div
id='opcuaiiot-server-tab-users' style='display:none'> <h4><span data-i18n='opcua-iiot-contrib.label.users'></span></h4>
<div class='form-row node-input-server-users-container-row' style='margin-bottom:0'> <div
id='node-input-server-users-container-div'
style='box-sizing:border-box;border-radius:5px;height:210px;padding:5px;border:1px solid #ccc;overflow-y:scroll'> <ol
id='node-input-server-users-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-server-users-add' style='margin-top:4px'><i
class='fa fa-plus'></i> <span data-i18n='opcua-iiot-contrib.label.addButton'></span></a> </div> <h4><span
data-i18n='opcua-iiot-contrib.label.xmlsets'></span></h4> <div class='form-row node-input-server-xmlsets-container-row'
style='margin-bottom:0'> <div id='node-input-server-xmlsets-container-div'
style='box-sizing:border-box;border-radius:5px;height:240px;padding:5px;border:1px solid #ccc;overflow-y:scroll'> <ol
id='node-input-server-xmlsets-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-server-xmlsets-add'
style='margin-top:4px'><i class='fa fa-plus'></i> <span data-i18n='opcua-iiot-contrib.label.addButton'></span></a>
</div> </div> <div id='opcuaiiot-server-tab-ass' style='display:none'> <div class='form-row' style='margin-bottom:0'>
<label for='node-input-addressSpaceScript' style='width:50%'><i class='fa fa-wrench'></i> <span
data-i18n='opcua-iiot-contrib.label.function'></span></label> <input type='hidden' id='node-input-addressSpaceScript'>
</div> <div class='form-row node-text-editor-row' id='div-script-editor'> <div style='height:450px;min-height:250px'
class='node-text-editor' id='node-input-func-editor-addressSpaceScript'></div> </div> </div> <div
id='opcuaiiot-server-tab-discovery' style='display:none'> <div class='form-row'> <!-- SecurityPolicy enum via REST -->
<label for='node-input-registerServerMethod' style='min-width:160px'><i class='fa fa-discover'></i> <span
data-i18n='opcua-iiot-contrib.label.registerServerMethod'></span></label> <select type='text'
id='node-input-registerServerMethod'> <option value='1'>HIDDEN</option> <option value='2'>MDNS</option> <option
value='3'>LDS</option> </select> </div> <div class='form-row'> <span style='min-width:160px'> <ul> <li>
HIDDEN: the server doesn't expose itself to the external world </li><li>
MDNS: the server publish itself to the mDNS Multicast network directly </li><li>
LDS: the server registers itself to the LDS or LDS-ME (Local Discovery Server) </li></ul> </span> </div> <div
class='form-row'> <label for='node-input-serverDiscovery' style='min-width:160px'><i class='fa fa-cc-discover'></i>
<span data-i18n='opcua-iiot-contrib.label.serverDiscovery'></span></label> <input type='checkbox'
id='node-input-serverDiscovery' style='max-width:35px'> </div> <div class='form-row'> <label
for='node-input-discoveryServerEndpointUrl' style='min-width:160px'><i class='icon-discover'></i> <span
data-i18n='opcua-iiot-contrib.label.discoveryServerEndpointUrl'></span></label> <input type='text'
id='node-input-discoveryServerEndpointUrl' placeholder='opc.tcp://localhost:4840'> </div> <div class='form-row'> <label
for='node-input-capabilitiesForMDNS' style='min-width:160px'><i class='icon-discover'></i> <span
data-i18n='opcua-iiot-contrib.label.capabilitiesForMDNS'></span></label> <input type='text'
id='node-input-capabilitiesForMDNS' placeholder='NA,DA,...'> </div> </div> </div> </script> <script type='text/x-red'
data-help-name='OPCUA-IIoT-Flex-Server'> <h2>OPC UA IIoT Flex Server</h2> <p>
The Flex Server node is an OPC UA server with a flexible address space to build your own information model. The address space is to expand with JS based on node-opcua API. The Flex Server did not work with ASO requests, but it works also with the Command node.
</p> <p>Default enpoint: opc.tcp://localhost:55380/</p> <p>
Named enpoint: opc.tcp://localhost:55380/UA/NodeREDFlexIIoTServer</p> <p>
Default discovery: opc.tcp://localhost:4840/UAFlexDiscovery</p> <p>all discovery: opc.tcp://localhost:4840/</p> <h3>
Options</h3> <p>If you need more information about all options read the node-opcua API, please!</p> <p>
The 'Delay On Close' is to close the server node with delay to all other nodes.</p> <h3>Certificates</h3> <p>
The server provides simple NodeOPCUA demo certificates and private keys.</p> <p>
You could set up your own certificate and private key files. This is optional.</p> <h3>User</h3> <p>
The user list is to set up users to the server. It is stored with Node-RED credentials best practice.</p> <h3>
XML NodeSets</h3> <p>That is static for now to add ISA95, DI, and Auto-ID to a server via XML NodeSet.</p> <h3>
AddressSpace</h3> <p>A function like programming editor to construct your own flexible address space in the server.</p>
<h3>Discovery</h3> <p>That gives you access to the servers discovery node-opcua parameters.</p> <p>
Set showErrors to get errors from node-opcua on browse.</p> </script>