UNPKG

prisme-flow

Version:

prisme platform flow engine

518 lines (444 loc) 16.2 kB
/** * Created by prisme.io on 09/06/2017. */ /** * OPC UA server with API to build ISA compliant information models. * @namespace ISAOpcUaServer * @module ISA95OpcUaServerNode */ module.exports = function (RED) { 'use strict' var opcua = require('node-opcua') // add server ISA95 extension to node-opcua require('node-opcua-isa95')(opcua) var isaMapping = require('./opcua/isamapping') var isaAddressSpace = require('./opcua/isaaddresssspace') var isaResultMessages = require('./core/isaconst').ISAResultMessage var isaOpcUa = require('./opcua/isaopcua') isaOpcUa.opcua = opcua isaMapping.isaOpcUa = isaOpcUa isaMapping.isaResultMessage = isaResultMessages isaMapping.dynamicNodes = {} isaAddressSpace.isaOpcUa = isaOpcUa isaAddressSpace.isaMapping = isaMapping isaAddressSpace.isaResultMessages = isaResultMessages var path = require('path') var os = require('os') var prettyjson = require('prettyjson') var pjOptions = { noColor: true } var EquipmentLevel = opcua.ISA95.EquipmentLevel /** * OPC UA server node capsule. * @memberof ISAOpcUaServer * @constructor */ function ISA95OpcUaServerNode (configNode) { RED.nodes.createNode(this, configNode) this.name = configNode.name this.port = configNode.port var node = this var examples var enterprises var initialized = false var server = null var serverAddressSpace function verbose_warn (logMessage) { if (RED.settings.verbose) { node.warn((node.name) ? node.name + ': ' + logMessage : 'OpcUaServerNode: ' + logMessage) } } function verbose_log (logMessage) { if (RED.settings.verbose) { node.log(logMessage) } } set_server_notrunning() // see node-opcua-isa95 doc var xmlFiles = [ opcua.standard_nodeset_file, opcua.ISA95.nodeset_file ] verbose_warn('node set:' + xmlFiles.toString()) /** * Initialization of a new OPC UA server. * @function */ function initNewServer () { initialized = false server = new opcua.OPCUAServer({ port: node.port, nodeset_filename: xmlFiles, resourcePath: 'UA/NodeRED/ISA95Server', buildInfo: { productName: node.name.concat('OPC UA server'), buildNumber: '1911', buildDate: new Date(2016, 12, 15) } }) server.initialize(post_initialize) } /** * Callback for server initialization work of node-opcua. * * Builds a static information model of ISA95 structures. * Here we also add all possibilities how to use node-opcua (variable, objects, methods ...). * * @function */ function post_initialize () { if (server) { console.time('initaddressspace') serverAddressSpace = server.engine.addressSpace if (!serverAddressSpace) { verbose_warn('post initialize - AddressSpace not ready to use') return } isaAddressSpace.serverAddressSpace = serverAddressSpace isaOpcUa.serverAddressSpace = serverAddressSpace var instantiateSampleISA95Model = require('./helpers/isa95_demo_address_space').instantiateSampleISA95Model instantiateSampleISA95Model(serverAddressSpace) isaAddressSpace.construct_my_address_space(serverAddressSpace) enterprises = isaOpcUa.addOrganizeFolder(serverAddressSpace.rootFolder.objects, 'Enterprises', 4) isaAddressSpace.build_enterprise_structure(enterprises, 5, 1) isaAddressSpace.build_enterprise_structure(enterprises, 6, 2) server.start(function () { server.endpoints[0].endpointDescriptions().forEach(function (endpoint) { verbose_warn('Server endpointUrl: ' + endpoint.endpointUrl + ' securityMode: ' + endpoint.securityMode.toString() + ' securityPolicyUri: ' + endpoint.securityPolicyUri.toString()) }) var endpointUrl = server.endpoints[0].endpointDescriptions()[0].endpointUrl verbose_log(' the primary server endpoint url is ' + endpointUrl) }) console.timeEnd('initaddressspace') set_server_running() initialized = true console.timeEnd('initserver') } else { verbose_warn('got no OPC UA server') set_server_error() } } /** * Set the server is running symbol on OPC UA server node. * @function */ function set_server_running () { node.status({fill: 'green', shape: 'dot', text: 'running'}) } /** * Set the server is stoped symbol on OPC UA server node. * @function */ function set_server_stoped () { node.status({fill: 'gray', shape: 'dot', text: 'not running'}) } function set_server_notrunning () { node.status({fill: 'red', shape: 'ring', text: 'Not running'}) } function set_server_error () { node.status({fill: 'red', shape: 'dot', text: 'error'}) } /** * Check if OPC UA server is ready to use. * @function */ function check_server_ready () { return (server && initialized) } /** * Find address space reference in the address space of the OPC UA server. * @function * @param {object} payload - payload of msg object */ function check_mapping (payload) { if (!check_server_ready()) { return isaResultMessages.ServerNotReady } switch (payload.mappingType) { case 'new': console.time('newmapping') for (var entry in payload.mappings) { if (entry) { new_mapping_entry_opcua(payload.mappings[entry].mapping) } else { verbose_log(JSON.stringify(entry) + " isn't a valid new object") } } console.timeEnd('newmapping') break case 'write': console.time('writemapping') var item for (var entry in payload.mappings) { if (entry) { item = payload.mappings[entry] if (item && item.hasOwnProperty('mapping') && isaMapping.search_mapped_to_read(item.mapping.structureParentNodeId, item.nodeId)) { switch (item.mapping.structureType) { case 'Variable': verbose_log('write variable ' + item.mapping.structureParentNodeId + ' ' + item.nodeId + ' ' + item.value + ' ' + item.datatype) break // TODO: add more types default: verbose_log('write ' + item.mapping.structureType + ' ' + item.mapping.structureParentNodeId + ' ' + item.nodeId + ' ' + item.datatype) } var itemOPCUA = isaMapping.search_mapped_to_write(item.mapping.structureParentNodeId, item.nodeId, item.value, item.datatype) verbose_log('write item: ' + JSON.stringify(itemOPCUA)) } } else { verbose_log(JSON.stringify(entry) + " isn't a valid existing object") } } console.timeEnd('writemapping') break default: verbose_warn('unknown mapping type ' + payload.mappingType) break } } /** * Add new node in the address space of the OPC UA server. * @function * @param {object} mapping - one mapping object */ function new_mapping_entry_opcua (mapping) { if (server.engine.addressSpace && serverAddressSpace !== server.engine.addressSpace) { verbose_warn('new server.engine.addressSpace') serverAddressSpace = server.engine.addressSpace if (!serverAddressSpace) { verbose_warn('post initialize - AddressSpace not ready to use') return } isaAddressSpace.serverAddressSpace = serverAddressSpace } else { if (!server.engine.addressSpace) { verbose_warn('server.engine.addressSpace invalid') } } var rootFolder = serverAddressSpace.findNode(mapping.structureParentNodeId) if (!rootFolder) { verbose_warn(mapping.structureNodeId + ' ParentNodeReferenceNotFound:' + mapping.structureParentNodeId) if (isaMapping.dynamicNodes[mapping.structureNodeId]) { delete isaMapping.dynamicNodes[mapping.structureNodeId] } return isaResultMessages.ParentNodeReferenceNotFound } else { verbose_log(mapping.structureNodeId + ' Reference found:' + mapping.structureParentNodeId) } if (!mapping.structureNodeId || !mapping.typeStructure) { verbose_warn(mapping + ' NodeIdNotValid') return isaResultMessages.NodeIdNotValid } var references switch (mapping.structureType) { case 'Variable': references = rootFolder.findReferences('HasComponent', true) break // TODO: add more types default: references = rootFolder.findReferences('Organizes', true) } if (references && !isaAddressSpace.findAddressSpaceReference(references, mapping.structureNodeId) && !isaMapping.dynamicNodes[mapping.structureNodeId]) { var item switch (mapping.structureType) { case 'Variable': verbose_log('new Variable ' + mapping.structureParentNodeId + ' ' + mapping.structureNodeId + ' ' + mapping.typeStructure) item = isaMapping.add_mapped_to_list(mapping.structureParentNodeId, mapping.structureNodeId, mapping.typeStructure) if (!isaAddressSpace.add_opcua_variable(rootFolder, mapping)) { verbose_warn('OPC UA variable not created') } break case 'Object': verbose_log('new Object ' + mapping.structureParentNodeId + ' ' + mapping.structureNodeId + ' ' + mapping.typeStructure) item = isaMapping.add_mapped_to_list(mapping.structureParentNodeId, mapping.structureNodeId, mapping.typeStructure) if (!isaAddressSpace.add_opcua_object(rootFolder, mapping)) { verbose_warn('OPC UA object not created') } break default: verbose_log('new Unknown without add ' + mapping.structureParentNodeId + ' ' + mapping.structureNodeId + ' ' + mapping.typeStructure) item = isaMapping.add_mapped_to_list(mapping.structureParentNodeId, mapping.structureNodeId, mapping.typeStructure) verbose_warn('OPC UA node will not be created') break } if (!item) { verbose_warn('no item for ' + mapping.structureNodeId) } } else { if (!references) { verbose_warn('references for new not valid') } if (isaAddressSpace.findAddressSpaceReference(references, mapping.structureNodeId)) { verbose_log('reference for new found ' + mapping.structureNodeId) } if (isaMapping.dynamicNodes[mapping.structureNodeId]) { verbose_log('dynamic node for new found ' + mapping.structureNodeId) } } } /** * Read message type from msg input payload. * @function * @param {object} payload - payload of msg object */ function read_message (payload) { switch (payload.messageType) { case 'Variable': if (payload.variableName == 'Counter') { isaAddressSpace.counterValue = payload.variableValue } break // TODO: add more messages to control OPC UA server default: break } } /** * Execute command from msg input payload. * @function * @param {object} payload - payload of msg object * @property payload.opcuaCommand.restartOPCUAServer - restarts the OPC UA server * @property payload.opcuaCommand.startOPCUAServer - restarts the OPC UA server * @property payload.opcuaCommand.stopOPCUAServer - stops the OPC UA server * @property payload.opcuaCommand.deleteNode - delete if found a node by nodeid in the OPC UA server's address space */ function execute_opcua_command (payload) { var name verbose_log('execute OPC UA command '.concat(payload.opcuaCommand)) switch (payload.opcuaCommand) { case 'restartOPCUAServer': case 'startOPCUAServer': restart_server() break case 'stopOPCUAServer': stop_server() break case 'deleteNode': if (!check_server_ready()) { verbose_warn('Server not active for deleting '.concat(payload.nodeName)) return } var searchedNode = serverAddressSpace.findNode(payload.nodeId) if (searchedNode === undefined) { verbose_warn('can not find Node in serverAddressSpace') } else { serverAddressSpace.deleteNode(searchedNode) } break // TODO: add more commands to give control over OPC UA server default: break } } /** * Reset structure of the ISA OPC UA server address space models. * @function */ function reset_structure () { verbose_log('reset structure') // manufacture enterprises = null // examples examples = null } /** * Reset internals of the ISA OPC UA server. * @function */ function reset_internals () { server = null reset_structure() reset_dynamic_nodes() verbose_log('reset internals done') } /** * Stop and shutdown the ISA OPC UA server. * @function */ function stop_server () { verbose_warn('Stop OPC UA Server') if (server) { server.shutdown(function (err) { if (err) { verbose_warn(JSON.stringify(err)) set_server_error() reset_internals() } else { set_server_stoped() reset_internals() } }) } else { verbose_warn('None OPC UA Server on close') reset_internals() set_server_notrunning() } } /** * Restart the ISA OPC UA server. * @function */ function restart_server () { if (server) { verbose_warn('Restart OPC UA Server') server.shutdown(function () { reset_internals() initNewServer() }) } else { verbose_warn('Start OPC UA Server') reset_internals() set_server_notrunning() initNewServer() } } console.time('initserver') initNewServer() /** * On input event of the ISA OPC UA server node. * @function */ node.on('input', function (msg) { var payload = msg.payload if (isaMapping.contains_opcua_command(payload)) { execute_opcua_command(payload) } else { try { if (check_server_ready()) { if (isaMapping.contains_mappingType(payload)) { check_mapping(payload) } if (isaMapping.contains_messageType(payload)) { read_message(payload) } } } catch (err) { node.error(err) } } node.send(msg) }) /** * On close event of the ISA OPC UA server node. * @function */ node.on('close', function () { verbose_warn('closing...') stop_server() verbose_log('closed') }) /** * Reset dynamic item list * @function */ function reset_dynamic_nodes () { verbose_log('reset dynamic nodes') isaMapping.dynamicNodes = {} } } RED.nodes.registerType('ISA95-OPCUA-Server', ISA95OpcUaServerNode) }