node-red-contrib-iiot-opcua
Version:
An Industrial IoT OPC UA toolbox contribution package for Node-RED based on node-opcua.
759 lines (661 loc) • 31.7 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 2013, 2016 IBM Corp. (node-red)
All rights reserved.
node-red-contrib-iiot-opcua
--> <style>
.opcuaiiotinject-time-row{padding-left:110px}.opcuaiiotinject-time-row select{margin:3px 0}.opcuaiiotinject-time-days label{-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;vertical-align:top;width:100px}.opcuaiiotinject-time-days input{width:auto}.opcuaiiotinject-time-times{width:90px}#opcuaiiotinject-time-time{width:75px}.opcuaiiotinject-time-count{width:40px}
</style> <script type='text/javascript'> RED.nodes.registerType('OPCUA-IIoT-Inject', {
category: 'IIoT',
color: '#ABCDEF',
defaults: {
injectType: {value: 'inject'},
payload: {
value: '', validate: function (v) {
let ptype = $('#node-input-payloadType').val() || this.payloadType
if (ptype === 'json') {
try {
JSON.parse(v)
return true
} catch (err) {
return false
}
} else if (ptype === 'flow' || ptype === 'global') {
return /^[a-zA-Z_][a-zA-Z0-9_]*(\.[a-zA-Z_][a-zA-Z0-9_]+)*/i.test(v)
} else if (ptype === 'num') {
return /^[+-]?[0-9]*\.?[0-9]*([eE][-+]?[0-9]+)?$/.test(v)
}
return true
}
},
payloadType: {value: 'date'},
topic: {value: ''},
repeat: {value: ''},
crontab: {value: ''},
once: {value: false},
startDelay: {value: ''},
name: {value: ''},
addressSpaceItems: {value: []}
},
inputs: 0,
outputs: 1,
align: 'left',
icon: 'icon.png'
})
let injectNode = RED.nodes.registry.getNodeType('OPCUA-IIoT-Inject')
injectNode.button = {
onclick: function () {
let label = (this.name || this.payload).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
if (this.payloadType === 'date') {
label = this._('opcuaiiotinject.timestamp')
}
if (this.payloadType === 'none') {
label = this._('opcuaiiotinject.blank')
}
let node = this
$.ajax({
url: 'opcuaIIoT/inject/' + this.id,
type: 'POST',
success: function (resp) {
RED.notify(node._('opcuaiiotinject.success', {label: label}), 'success')
},
error: function (jqXHR, textStatus, errorThrown) {
if (jqXHR.status === 404) {
RED.notify(node._('common.notification.error', {message: node._('common.notification.errors.not-deployed')}), 'error')
} else if (jqXHR.status === 500) {
RED.notify(node._('common.notification.error', {message: node._('opcuaiiotinject.errors.failed')}), 'error')
} else if (jqXHR.status === 0) {
RED.notify(node._('common.notification.error', {message: node._('common.notification.errors.no-response')}), 'error')
} else {
RED.notify(node._('common.notification.error', {
message: node._('common.notification.errors.unexpected', {
status: jqXHR.status,
message: textStatus
})
}), 'error')
}
}
})
}
}
injectNode.oneditprepare = function () {
let node = this
let fieldPayloadType = $('#node-input-payloadType')
if (this.payloadType === null) {
if (this.payload === '') {
this.payloadType = 'date'
} else {
this.payloadType = 'str'
}
} else if (this.payloadType === 'string' || this.payloadType === 'none') {
this.payloadType = 'str'
}
fieldPayloadType.val(this.payloadType)
$('#node-input-payload').typedInput({
default: 'str',
typeField: fieldPayloadType,
types: ['flow', 'global', 'str', 'num', 'bool', 'json', 'date']
})
$('#opcuaiiotinject-time-type-select').change(function () {
$('#node-input-crontab').val('')
let id = $('#opcuaiiotinject-time-type-select').val()
$('.opcuaiiotinject-time-row').hide()
$('#opcuaiiotinject-time-row-' + id).show()
if ((id === 'none') || (id === 'interval')) {
$('#node-once').show()
$('#node-delay').show()
}
else {
$('#node-once').hide()
$('#node-delay').hide()
$('#node-input-once').prop('checked', false)
}
})
$('.opcuaiiotinject-time-times').each(function () {
for (let i = 0; i < 24; i++) {
let l = (i < 10 ? '0' : '') + i + ':00'
$(this).append($('<option></option>').val(i).text(l))
}
})
$('<option></option>').val(24).text('00:00').appendTo('#opcuaiiotinject-time-interval-time-end')
$('#opcuaiiotinject-time-interval-time-start').change(function () {
let start = Number($('#opcuaiiotinject-time-interval-time-start').val())
let end = Number($('#opcuaiiotinject-time-interval-time-end').val())
$('#opcuaiiotinject-time-interval-time-end option').remove()
for (let i = start + 1; i < 25; i++) {
let l = (i < 10 ? '0' : '') + i + ':00'
if (i === 24) {
l = '00:00'
}
let opt = $('<option></option>').val(i).text(l).appendTo('#opcuaiiotinject-time-interval-time-end')
if (i === end) {
opt.attr('selected', 'selected')
}
}
})
$('.opcuaiiotinject-time-count').spinner({
min: 1
})
$.widget('ui.injecttimespinner', $.ui.spinner, {
options: {
// seconds
step: 60 * 1000,
// hours
page: 60
},
_parse: function (value) {
if (typeof value === 'string') {
// already a timestamp
if (Number(value) === value) {
return Number(value)
}
let p = value.split(':')
let offset = new Date().getTimezoneOffset()
return ((Number(p[0]) * 60) + Number(p[1]) + offset) * 60 * 1000
}
return value
},
_format: function (value) {
let d = new Date(value)
let h = d.getHours()
let m = d.getMinutes()
return ((h < 10) ? '0' : '') + h + ':' + ((m < 10) ? '0' : '') + m
}
})
$('#opcuaiiotinject-time-time').injecttimespinner()
let repeattype = 'none'
if (this.repeat !== '') {
repeattype = 'interval'
let units = 's'
let timeRange = this.repeat
if (this.repeat % 60 === 0) {
units = 'm'
timeRange = timeRange / 60
}
if (this.repeat % 1440 === 0) {
units = 'h'
timeRange = timeRange / 60
}
$('#opcuaiiotinject-time-interval-count').val(timeRange)
$('#opcuaiiotinject-time-interval-units').val(units)
$('#opcuaiiotinject-time-interval-days').prop('disabled', 'disabled')
} else if (this.crontab !== '') {
let cronparts = this.crontab.split(' ')
let days = cronparts[4]
if (!isNaN(cronparts[0]) && !isNaN(cronparts[1])) {
repeattype = 'time'
// Fixed time
let time = cronparts[1] + ':' + cronparts[0]
$('#opcuaiiotinject-time-time').val(time)
$('#opcuaiiotinject-time-type-select').val('s')
if (days === '*') {
$('#opcuaiiotinject-time-time-days input[type=checkbox]').prop('checked', true)
} else {
$('#opcuaiiotinject-time-time-days input[type=checkbox]').removeAttr('checked')
days.split(',').forEach(function (v) {
$('#opcuaiiotinject-time-time-days [value=' + v + ']').prop('checked', true)
})
}
} else {
repeattype = 'interval-time'
// interval - time period
let minutes = cronparts[0].slice(2)
if (minutes === '') {
minutes = '0'
}
$('#opcuaiiotinject-time-interval-time-units').val(minutes)
if (days === '*') {
$('#opcuaiiotinject-time-interval-time-days input[type=checkbox]').prop('checked', true)
} else {
$('#opcuaiiotinject-time-interval-time-days input[type=checkbox]').removeAttr('checked')
days.split(',').forEach(function (v) {
$('#opcuaiiotinject-time-interval-time-days [value=' + v + ']').prop('checked', true)
})
}
let time = cronparts[1]
let timeparts = time.split(',')
let start
let end
if (timeparts.length === 1) {
// 0 or 0-10
let hours = timeparts[0].split('-')
if (hours.length === 1) {
if (hours[0] === '') {
start = '0'
end = '0'
}
else {
start = hours[0]
end = Number(hours[0]) + 1
}
} else {
start = hours[0]
end = Number(hours[1]) + 1
}
} else {
// 23,0 or 17-23,0-10 or 23,0-2 or 17-23,0
let startparts = timeparts[0].split('-')
start = startparts[0]
let endparts = timeparts[1].split('-')
if (endparts.length === 1) {
end = Number(endparts[0]) + 1
} else {
end = Number(endparts[1]) + 1
}
}
$('#opcuaiiotinject-time-interval-time-end').val(end)
$('#opcuaiiotinject-time-interval-time-start').val(start)
}
} else {
repeattype = 'none'
}
$('.opcuaiiotinject-time-row').hide()
$('#opcuaiiotinject-time-type-select').val(repeattype)
$('#opcuaiiotinject-time-row-' + repeattype).show()
$('#node-input-payload').typedInput('type', this.payloadType)
$('#opcuaiiotinject-time-type-select').change()
$('#opcuaiiotinject-time-interval-time-start').change()
$('#node-input-injectType').change(function () {
if ($(this).val() === 'listen') {
$('#input-label-listener').show()
$('#input-label-payload').hide()
if(fieldPayloadType.val() !== 'num' && fieldPayloadType.val() !== 'json') {
$('#node-input-payload').typedInput('type', 'num')
$('#node-input-payload').val('1000')
}
} else {
$('#input-label-payload').show()
$('#input-label-listener').hide()
}
})
$('#node-input-once').change(function () {
if ($(this).is(':checked') || $('#opcuaiiotinject-time-type-select').val() !== 'none') {
$('#node-delay').show()
} else {
$('#node-delay').hide()
}
})
let tabs = RED.tabs.create({
id: 'node-input-inject-tabs',
onchange: function (tab) {
$('#node-input-inject-tabs-content').children().hide()
$('#' + tab.id).show()
}
})
tabs.addTab({
id: 'opcuaiiotinject-tab-settings',
label: this._('opcuaiiotinject.tabs-label.settings')
})
tabs.addTab({
id: 'opcuaiiotinject-tab-addressSpaceItems',
label: this._('opcuaiiotinject.tabs-label.addressSpaceItems')
})
let cacheItemCount = 0
if (node.addressSpaceItems && node.addressSpaceItems.length > 0) {
cacheItemCount = node.addressSpaceItems.length
node.addressSpaceItems.forEach(function (element, index, array) {
generateItemsEntry(element, index)
})
}
function generateItemsEntry (opcuaItem, 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 itemNameField = $('<input/>', {
class: 'addressSpaceItemName',
id: 'node-input-addressSpaceItems-name' + id,
type: 'text',
style: 'margin: 0 auto;width:20%;min-width:60px;margin-left:5px',
placeholder: 'Name'
}).appendTo(row)
let itemNodIdField = $('<input/>', {
class: 'addressSpaceItemNodeId',
id: 'node-input-addressSpaceItems-nodeId' + id,
type: 'text',
style: 'margin: 0 auto;width:43%;min-width:60px;margin-left:5px',
placeholder: 'ns=0;i=85'
}).appendTo(row)
let itemDatatypeField = $('<input/>', {
class: 'addressSpaceItemDatatype',
id: 'node-input-addressSpaceItems-datatype' + id,
type: 'text',
style: 'margin: 0 auto;width:18%;min-width:60px;margin-left:5px',
placeholder: 'optional'
}).appendTo(row)
itemNameField.val(opcuaItem.name)
itemNodIdField.val(opcuaItem.nodeId)
itemDatatypeField.val(opcuaItem.datatypeName)
let finalspan = $('<span/>', {style: 'float: right;margin-right: 10px;'}).appendTo(row)
let lookupItemButton = $('<a/>', {
href: '#',
id: 'node-button-inject-datatypes' + id,
class: 'editor-button editor-button-small listButton',
style: 'margin-top: 7px; margin-left: 5px;'
}).appendTo(finalspan)
lookupItemButton.click(function () {
let datatypesLookupButton = $('#node-button-inject-datatypes' + id)
let datatypeLookupField = $('#row' + id + ' #node-input-addressSpaceItems-datatype' + id)
datatypesLookupButton.addClass('disabled')
$.getJSON('/opcuaIIoT/plain/DataTypeIds', function (data) {
datatypesLookupButton.removeClass('disabled')
node.datatypeNames = []
$.each(data, function (i, datatypeName) {
node.datatypeNames.push(datatypeName)
})
datatypeLookupField.autocomplete({
source: node.datatypeNames,
minLength: 0,
close: function (event, ui) {
datatypeLookupField.autocomplete('destroy')
}
}).autocomplete('search', '')
})
})
$('<i/>', {class: 'fa fa-search'}).appendTo(lookupItemButton)
let removeItemButton = $('<a/>', {
href: '#',
id: 'node-button-inject-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-addressSpaceItems-container').append(container)
}
$('#node-input-addressSpaceItems-container').sortable({
axis: 'y',
handle: '.node-input-addressSpaceItems-handle',
cursor: 'move'
})
$('#node-input-addressSpaceItems-container .node-input-addressSpaceItems-handle').disableSelection()
$('#node-input-addressSpaceItems-add').click(function () {
if(!cacheItemCount || cacheItemCount < 0) {
cacheItemCount = 0
}
generateItemsEntry({name: '', nodeId: '', datatypeName: ''}, cacheItemCount++) // length is every time one more than index
$('#node-input-addressSpaceItems-container-div').scrollTop($('#node-input-addressSpaceItems-container-div').get(0).scrollHeight)
})
function switchDialogResize () {
let rows = $('#dialog-form>div:not(.node-input-addressSpaceItems-container-row)')
let height = $('#dialog-form').height()
rows.each(function (index, row) {
height -= row.outerHeight(true)
})
let editorRow = $('#dialog-form>div.node-input-addressSpaceItems-container-row')
height -= (parseInt(editorRow.css('marginTop')) + parseInt(editorRow.css('marginBottom')))
$('#node-input-addressSpaceItems-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)
})
// console.log('well done editprepare ...')
}
injectNode.oneditsave = function () {
let node = this
let repeat = ''
let crontab = ''
let type = $('#opcuaiiotinject-time-type-select').val()
if (type === 'interval') {
crontab = ''
let count = $('#opcuaiiotinject-time-interval-count').val()
let units = $('#opcuaiiotinject-time-interval-units').val()
if (units === 's') {
repeat = count
} else {
if (units === 'n') {
repeat = count / 1000
} else if (units === 'm') {
repeat = count * 60
} else if (units === 'h') {
repeat = count * 60 * 60
}
}
} else if (type === 'interval-time') {
repeat = ''
let count = $('#opcuaiiotinject-time-interval-time-units').val()
let startTime = Number($('#opcuaiiotinject-time-interval-time-start').val())
let endTime = Number($('#opcuaiiotinject-time-interval-time-end').val())
let days = $('#opcuaiiotinject-time-interval-time-days input[type=checkbox]:checked').map(function (_, el) {
return $(el).val()
}).get()
if (days.length === 0) {
crontab = ''
} else {
if (days.length === 7) {
days = '*'
} else {
days = days.join(',')
}
let timerange = ''
if (endTime === 0) {
timerange = startTime + '-23'
} else if (startTime + 1 < endTime) {
timerange = startTime + '-' + (endTime - 1)
} else if (startTime + 1 === endTime) {
timerange = startTime
} else {
let startpart = ''
let endpart = ''
if (startTime === 23) {
startpart = '23'
} else {
startpart = startTime + '-23'
}
if (endTime === 1) {
endpart = '0'
} else {
endpart = '0-' + (endTime - 1)
}
timerange = startpart + ',' + endpart
}
if (count === '0') {
crontab = count + ' ' + timerange + ' * * ' + days
} else {
crontab = '*/' + count + ' ' + timerange + ' * * ' + days
}
}
} else if (type === 'time') {
let time = $('#opcuaiiotinject-time-time').val()
let days = $('#opcuaiiotinject-time-time-days input[type=checkbox]:checked').map(function (_, el) {
return $(el).val()
}).get()
if (days.length === 0) {
crontab = ''
} else {
if (days.length === 7) {
days = '*'
} else {
days = days.join(',')
}
let parts = time.split(':')
repeat = ''
crontab = parts[1] + ' ' + parts[0] + ' * * ' + days
}
}
$('#node-input-repeat').val(repeat)
$('#node-input-crontab').val(crontab)
let cacheItems = $('#node-input-addressSpaceItems-container').children()
node.addressSpaceItems = []
cacheItems.each(function () {
node.addressSpaceItems.push({
name: $(this).find('.addressSpaceItemName').val(),
nodeId: $(this).find('.addressSpaceItemNodeId').val(),
datatypeName: $(this).find('.addressSpaceItemDatatype').val()
})
})
// console.log('well done editsave ...')
}
injectNode.label = function () {
let suffix = ''
if (this.once) {
suffix = ' ¹'
}
let hasRepeat = ( this.repeat && this.repeat !== 'none' )
if (hasRepeat || this.crontab) {
suffix = suffix + ' ↻'
}
if (this.name) {
return this.name + suffix
}
switch (this.payloadType) {
case 'string':
case 'str':
case 'num':
case 'bool':
case 'json':
if (this.topic !== '' && (this.topic.length + this.payload.length) <= 32) {
return this.topic + ':' + this.payload + suffix
} else if (this.payload.length > 0 && this.payload.length < 24) {
return this.payload + suffix
}
return this._('opcuaiiotinject.inject') + suffix
case 'date':
if (this.topic !== '' && this.topic.length <= 16) {
return this.topic + ':' + this._('opcuaiiotinject.timestamp') + suffix
}
return this._('opcuaiiotinject.timestamp') + suffix
default:
if (this.payloadType === 'flow' && this.payload.length < 19) {
return 'flow.' + this.payload + suffix
} else if (this.payloadType === 'global' && this.payload.length < 17) {
return 'global.' + this.payload + suffix
}
return this._('opcuaiiotinject.inject') + suffix
}
}
injectNode.labelStyle = function () {
return this.name ? 'node_label_italic' : ''
} </script> <script type='text/x-red' data-template-name='OPCUA-IIoT-Inject'>
<div class='form-row'> <ul style='background:#fff;min-width:600px;margin-bottom:20px' id='node-input-inject-tabs'></ul>
</div> <div id='node-input-inject-tabs-content' style='min-height:170px'> <div id='opcuaiiotinject-tab-settings'
style='display:none'> <div class='form-row' id='node-inject-type'> <label for=''><i class='fa fa-list'></i> <span
data-i18n='opcuaiiotinject.label.injectType'></span></label> <select id='node-input-injectType' type='text'
style='width:140px'> <option value='inject' data-i18n='opcuaiiotinject.type-label.inject'></option> <option
value='read' data-i18n='opcuaiiotinject.type-label.read'></option> <option value='write'
data-i18n='opcuaiiotinject.type-label.write'></option> <option value='listen'
data-i18n='opcuaiiotinject.type-label.listen'></option> </select> </div> <div class='form-row' id='input-payload-row'>
<label for='node-input-payload'><i class='fa fa-envelope'></i> <span id='input-label-payload'
data-i18n='opcuaiiotinject.label.payload'></span> <span id='input-label-listener'
data-i18n='opcuaiiotinject.label.interval'></span> </label> <input type='text' id='node-input-payload'
style='width:70%'> <input type='hidden' id='node-input-payloadType'> </div> <div class='form-row'> <label
for='node-input-topic'><i class='icon-tag'></i> <span data-i18n='node-red:common.label.topic'></span></label> <input
type='text' id='node-input-topic'> </div> <div class='form-row' id='node-delay'> <label for='node-input-startDelay'><i
class='fa fa-clock-o'></i> <span data-i18n='opcuaiiotinject.startDelay'></span></label> <input type='text'
id='node-input-startDelay' placeholder='10' style='width:60px'> <span data-i18n='opcuaiiotinject.startDelayAddon'>
</span> </div> <div class='form-row' id='node-once'> <label for='node-input-once' style='width:160px'> <i
class='fa fa-power-off'></i> <span data-i18n='opcuaiiotinject.onstart'></span></label> <input type='checkbox'
id='node-input-once' style='width:40px'> </div> <div class='form-row'> <label for=''><i class='fa fa-repeat'></i> <span
data-i18n='opcuaiiotinject.label.repeat'></span></label> <select id='opcuaiiotinject-time-type-select'> <option
value='none' data-i18n='opcuaiiotinject.none'></option> <option value='interval' data-i18n='opcuaiiotinject.interval'>
</option> <option value='interval-time' data-i18n='opcuaiiotinject.interval-time'></option> <option value='time'
data-i18n='opcuaiiotinject.time'></option> </select> <input type='hidden' id='node-input-repeat'> <input type='hidden'
id='node-input-crontab'> </div> <div class='form-row opcuaiiotinject-time-row hidden'
id='opcuaiiotinject-time-row-interval'> <span data-i18n='opcuaiiotinject.every'></span> <input
id='opcuaiiotinject-time-interval-count' class='opcuaiiotinject-time-count' value='500'> <select style='width:140px'
id='opcuaiiotinject-time-interval-units'> <option value='n' data-i18n='opcuaiiotinject.milliseconds'></option> <option
value='s' data-i18n='opcuaiiotinject.seconds'></option> <option value='m' data-i18n='opcuaiiotinject.minutes'></option>
<option value='h' data-i18n='opcuaiiotinject.hours'></option> </select><br> </div> <div
class='form-row opcuaiiotinject-time-row hidden' id='opcuaiiotinject-time-row-interval-time'> <span
data-i18n='opcuaiiotinject.every'></span> <select style='width:90px' id='opcuaiiotinject-time-interval-time-units'
class='opcuaiiotinject-time-int-count' value='1'> <option value='1'>1</option> <option value='2'>2</option> <option
value='3'>3</option> <option value='4'>4</option> <option value='5'>5</option> <option value='6'>6</option> <option
value='10'>10</option> <option value='12'>12</option> <option value='15'>15</option> <option value='20'>20</option>
<option value='30'>30</option> <option value='0'>60</option> </select> <span data-i18n='opcuaiiotinject.minutes'></span>
<br> <span data-i18n='opcuaiiotinject.between'></span> <select id='opcuaiiotinject-time-interval-time-start'
class='opcuaiiotinject-time-times'></select> <span data-i18n='opcuaiiotinject.and'></span> <select
id='opcuaiiotinject-time-interval-time-end' class='opcuaiiotinject-time-times'></select><br> <div
id='opcuaiiotinject-time-interval-time-days' class='opcuaiiotinject-time-days'> <div
style='display:inline-block vertical-align: topmargin-right:5px' data-i18n='opcuaiiotinject.on'>on</div> <div
style='display:inline-block'> <div> <label><input type='checkbox' checked='checked' value='1'> <span
data-i18n='opcuaiiotinject.days.0'></span></label> <label><input type='checkbox' checked='checked' value='2'> <span
data-i18n='opcuaiiotinject.days.1'></span></label> <label><input type='checkbox' checked='checked' value='3'> <span
data-i18n='opcuaiiotinject.days.2'></span></label> </div> <div> <label><input type='checkbox' checked='checked'
value='4'> <span data-i18n='opcuaiiotinject.days.3'></span></label> <label><input type='checkbox' checked='checked'
value='5'> <span data-i18n='opcuaiiotinject.days.4'></span></label> <label><input type='checkbox' checked='checked'
value='6'> <span data-i18n='opcuaiiotinject.days.5'></span></label> </div> <div> <label><input type='checkbox'
checked='checked' value='0'> <span data-i18n='opcuaiiotinject.days.6'></span></label> </div> </div> </div> </div> <div
class='form-row opcuaiiotinject-time-row hidden' id='opcuaiiotinject-time-row-time'> <span
data-i18n='opcuaiiotinject.at'></span> <input id='opcuaiiotinject-time-time' value='12:00'><br> <div
id='opcuaiiotinject-time-time-days' class='opcuaiiotinject-time-days'> <div
style='display:inline-block vertical-align: topmargin-right: 5px'>on </div> <div style='display:inline-block'> <div>
<label><input type='checkbox' checked='checked' value='1'> <span data-i18n='opcuaiiotinject.days.0'></span></label>
<label><input type='checkbox' checked='checked' value='2'> <span data-i18n='opcuaiiotinject.days.1'></span></label>
<label><input type='checkbox' checked='checked' value='3'> <span data-i18n='opcuaiiotinject.days.2'></span></label>
</div> <div> <label><input type='checkbox' checked='checked' value='4'> <span data-i18n='opcuaiiotinject.days.3'></span>
</label> <label><input type='checkbox' checked='checked' value='5'> <span data-i18n='opcuaiiotinject.days.4'></span>
</label> <label><input type='checkbox' checked='checked' value='6'> <span data-i18n='opcuaiiotinject.days.5'></span>
</label> </div> <div> <label><input type='checkbox' checked='checked' value='0'> <span
data-i18n='opcuaiiotinject.days.6'></span></label> </div> </div> </div> </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' data-i18n='[placeholder]node-red:common.label.name'> </div> <div class='form-tips'
data-i18n='[html]opcuaiiotinject.tip'></div> </div> <div id='opcuaiiotinject-tab-addressSpaceItems'
style='display:none'> <div class='form-row node-input-addressSpaceItems-container-row' style='margin-bottom:0'> <div
id='node-input-addressSpaceItems-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-addressSpaceItems-container' style='list-style-type:none;margin:0 auto;width:95%;margin-left:20px'></ol>
</div> </div> <div class='form-row'> <a href='#' class='editor-button editor-button-small'
id='node-input-addressSpaceItems-add' style='margin-top:4px'><i class='fa fa-plus'></i> <span
data-i18n='opcuaiiotinject.label.addButton'></span></a> </div> </div> </div> </script> <script type='text/x-red'
data-help-name='OPCUA-IIoT-Inject'> <h2>OPC UA IIoT Inject</h2> <p>
The modified Inject node is designed to provide some unique features you need to create beautiful flows.</p> <p>
Pressing the button on the left side of the node builds an inject object to read, browse, listen or write by the OPC UA IIoT nodes.
</p> <h2>Configuration</h2> <h3>Tab Settings</h3> <strong>Type</strong> <p>
Set that type to prepare the correct value for injects.</p> <strong>Payload/Interval</strong> <p>
There is an option to send a JSON payload inject into the Listener.</p> <p>Example:</p> <p><pre>
{ "interval": 500, "queueSize": 10 }</pre></p> <div> <h5>msg.payload.options<h5> <strong>
event subscription parameters default: <pre>
msg.payload.options = {
requestedPublishingInterval: 100,
requestedLifetimeCount: 60,
requestedMaxKeepAliveCount: 10,
maxNotificationsPerPublish: 4,
publishingEnabled: true,
priority: 3
}
</pre> <strong>subscription parameters default: <pre>
msg.payload.options = {
requestedPublishingInterval: 100,
requestedLifetimeCount: 1000,
requestedMaxKeepAliveCount: 12,
maxNotificationsPerPublish: 100,
publishingEnabled: true,
priority: 6
}
</pre> </strong></strong></h5></h5></div> <p>
If the Inject-Type is set to read/write, then the value is a standard payload to transport your data.</p> <strong>Topic
</strong> <p>The topic is to group for queues or protocols like MQTT.</p> <strong>Repeat</strong> <p>
With the repeat settings you can set a cron configuration.</p> <p>
The interval settings needs a short delay on start. This is equal to the once option but without any inject at to start working.
</p> <strong>Inject once at start</strong> <p>
The 'Delay sec. at start' has to be set to a minimum for starting injects toward the OPC UA server.</p> <p>
All activities will start ever <strong>after</strong> the start inject is done.</p> <p>
I did a contribute to Node-RED to provide that delay at start from Node-RED 0.18 inject too.</p> <strong>Name</strong>
<p>Name in the flow of Node-RED.</p> <h3>Tab OPC UA Items</h3> <strong>Name (optional)</strong> <p>
A short human readable name for long or numeric nodeId's.</p> <strong>Node-Id (mandatory)</strong> <p>
The Node-Id has to be an OPC UA valid Node-Id like ns=0;i=85 (root->Objects)</p> <p>
You can use our Browser node for OPC UA to find the correct Node-Id.</p> <strong>Data Type (mandatory/optional)</strong>
<p>The Data Type setting is needed to do the converts of the node-opcua Variant type result(s).</p> <p>
It is mandatory to write data otherwise it is optional!</p> <p>
In best practice do transformations with the Result Filter node.</p> <div> <strong>Content of an inject:</strong> <ul>
<li>payload (value or options for listener) </li><li>topic </li><li>nodetype (inject, read, write, listen) </li><li>
injectType (inject) </li><li>addressSpaceItems (Array of nodeId's) </li></ul> </div> </script>