UNPKG

@p2olab/pimad-core

Version:

PiMAd (Process-industry-Modular-Automation-description) High level access to automation of modular plants.

504 lines (503 loc) 24.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.LastChainElementImporterFactory = exports.MTPFreeze202001ImporterFactory = exports.MTPFreeze202001Importer = exports.LastChainLinkImporter = void 0; const Utils_1 = require("../../Utils"); const Backbone_1 = require("../../Backbone"); const ImporterPart_1 = require("./ImporterPart"); const GateFactory_1 = require("../Gate/GateFactory"); const ModuleAutomation_1 = require("../../ModuleAutomation"); const ModuleAutomation_2 = require("../../ModuleAutomation"); const ModuleAutomation_3 = require("../../ModuleAutomation"); var PiMAdResponseVendor = Backbone_1.Backbone.PiMAdResponseVendor; var DataAssemblyVendor = ModuleAutomation_1.ModuleAutomation.DataAssemblyVendor; const uuid_1 = require("uuid"); const ServiceModel_1 = require("../../ModuleAutomation/ServiceModel"); class AImporter { constructor() { this.initialized = false; this.metaModelVersion = new Backbone_1.BasicSemanticVersion(); this.nextImporter = undefined; this.responseVendor = new PiMAdResponseVendor(); this.metaModelVersion.initialize(0, 0, 1); } getMetaModelVersion() { return this.metaModelVersion; } initialize(nextImporter) { if (!this.initialized) { this.nextImporter = nextImporter; this.initialized = (JSON.stringify(this.nextImporter) == JSON.stringify(nextImporter)); return this.initialized; } else { return false; } } } /** * Last link of every importer chain. Cleans up and build a meaningful answer why the import has failed. */ class LastChainLinkImporter extends AImporter { /** * All prioritized importers could not perform the import. Error message with debug information is created and * returned to the calling one. * * @param instructions - A set of instructions, configuring the importer. * @param callback - Passing the result back via a callback function. */ convertFrom(instructions, callback) { callback(this.responseVendor.buyErrorResponse()); } /** * Initializing the LastChainLink. * @param nextImporter - The next Importer element in the chain, but this is already the last element! Therefore the * object will not be stored. You could also pass an 'undefined' here. */ initialize(nextImporter) { if (!this.initialized) { if (nextImporter != undefined) { Utils_1.logger.warn('You pass an Importer to a LastChainLinkImporter. That is not necessary. Use undefined instead.'); } this.initialized = true; return true; } else { return false; } } } exports.LastChainLinkImporter = LastChainLinkImporter; /** * Importer for MTPFreeze 2020.01. */ class MTPFreeze202001Importer extends AImporter { constructor() { super(); this.servicePart = new ImporterPart_1.ServicePart(); this.hmiPart = new ImporterPart_1.HMIPart(); this.mtpPart = new ImporterPart_1.MTPPart(); this.textPart = new ImporterPart_1.TextPart(); // Factories this.amlGateFactory = new GateFactory_1.AMLGateFactory(); this.dataAssemblyVendor = new DataAssemblyVendor(); this.mtpGateFactory = new GateFactory_1.MTPGateFactory(); this.peaFactory = new ModuleAutomation_2.BasePEAFactory(); this.procedureFactory = new ModuleAutomation_3.BaseProcedureFactory(); this.serviceFactory = new ServiceModel_1.BaseServiceFactory(); this.xmlGateFactory = new GateFactory_1.XMLGateFactory(); this.zipGateFactory = new GateFactory_1.ZIPGateFactory(); } /** @inheritDoc */ convertFrom(instructions, callback) { if (this.initialized) { // this.followInstructions(instructions, response => { callback(response); }); } else { const notInitialized = this.responseVendor.buyErrorResponse(); Utils_1.logger.error('Use of a non-initialized MTPFreeze202001Importer. This one rejects the Request!'); notInitialized.initialize('The Importer is not initialized yet! Aborting ... ', {}); callback(notInitialized); } } /** * This method evaluates the instructions to the importer. Valid instructions are forwarded to {@link accessDataSource} to gain * access to the source and the data stored in it. * * Answers the first question of the activity {@link Importer.convertFrom} for this importer. * * @param instructions - Pass the address of the source via the source attribute of the object. * @param callback - Returns a successful {@link SuccessResponse} with PEAModel or an {@link ErrorResponse} with * a message why this step has finally failed. */ followInstructions(instructions, callback) { var _a; // Instructions if (instructions.source != '') { // access data source this.accessDataSource(instructions, response => { callback(response); }); } else { (_a = this.nextImporter) === null || _a === void 0 ? void 0 : _a.convertFrom(instructions, response => { callback(response); }); } } /** * This method reads the data from the source and converts it into a JSON-object, which is then passed to the * {@link checkInformationModel} to check the meta model. * * Answers the second question of the activity {@link Importer.convertFrom} for this importer. * * @param instructions - Pass the address of the source via the source attribute of the object. * @param callback - Returns a successful {@link SuccessResponse} with PEAModel or an {@link ErrorResponse} with * a message why this step has finally failed. * @private */ accessDataSource(instructions, callback) { let gate = new GateFactory_1.MockGateFactory().create(); switch (instructions.source.split('.').pop()) { case 'aml': gate = this.amlGateFactory.create(); break; case 'mtp': gate = this.mtpGateFactory.create(); break; case 'xml': gate = this.xmlGateFactory.create(); break; case 'zip': gate = this.zipGateFactory.create(); break; default: break; } if (gate.constructor.name === new GateFactory_1.MockGateFactory().create().constructor.name) { const unknownSourceType = this.responseVendor.buyErrorResponse(); unknownSourceType.initialize('Unknown source type <' + instructions.source + '>', {}); callback(unknownSourceType); } else { gate.initialize(instructions.source); gate.receive({}, response => { var _a; //TODO > Fix gate + address issue in Gate.ts //TODO > Fix that array shit. const localCAEXFile = {}; const caexFile = response.getContent(); if (Array.isArray(caexFile.data)) { localCAEXFile.data = caexFile.data[0].data; } else { localCAEXFile.data = caexFile.data; } if (((_a = localCAEXFile.data) === null || _a === void 0 ? void 0 : _a.CAEXFile) != undefined) { this.checkInformationModel(localCAEXFile.data.CAEXFile, instructions.identifier, checkIMResponse => { callback(checkIMResponse); }); } else { const followInstructionResponse = this.responseVendor.buyErrorResponse(); followInstructionResponse.initialize('The File at ' + instructions.source + ' is not valid CAEX!', {}); callback(followInstructionResponse); } }); } } /** * Uff... actually there is no real possibility to check IM of MTP. Missing SemVer. Therefore passing to the * next stage. * * Answers the third question of the activity {@link Importer.convertFrom} for this importer. * * @param data - The content of a CAEXFile as a JSON-object. * @param pimadIdentifier - ??? * @param callback - TODO after rework! * @private */ checkInformationModel(data, pimadIdentifier, callback) { this.convert(data, pimadIdentifier, response => { callback(response); }); } /** * Get a specific ModuleTypePackageSet-Set. * @param refBaseSystemUnitPath - The meta model path. * @param array - An array with ModuleTypePackage-Sets * @param callback - ??? * @private */ getSet(refBaseSystemUnitPath, array, callback) { array.forEach((content) => { if (refBaseSystemUnitPath === content.RefBaseSystemUnitPath) { callback(content); } }); } /** * This method translates the interface description of the ModuleTypePackage within the CAEX file into a PEAModel of the * PiMAd-core-IM. After the different MTP parts are extracted by {@link ImporterPart}s, DataAssemblies, Services * and Procedures are merged. The reference system of the MTP is used for this. The import of the MTP is then * completed. * * Detailed inline code documentation exists for this method. Nevertheless, it is important to read the MTP * standard. * * <uml> * skinparam shadowing false * partition "convert" { * start * :prepare pea-data-storage; * :loop through the InstanceHierarchy\nand extract data for different\nModuleTypePackage parts; * if(extracted data\nis valid?) then (no) * :callback an ErrorResponse; * stop * else (yes) * while (are there\nmore services?) is (yes) * :take next service; * :get and store the\nreferenced DataAssembly; * if (is referenced DataAssembly undefined?) then (yes) * :skipping this service; * else (no) * :prepare service-data-storage; * while (are there\nmore procedures?) is (yes) * :take next procedure; * :get and store the\nreferenced DataAssembly; * if (is referenced DataAssembly undefined?) then (yes) * :skipping this procedure; * else (no) * :initialize procedure; * if (initialization successful?) then (yes) * :push procedure to\nservice-data-storage; * endif * endif * endwhile (no) * :initialize service; * if (initialization successful?) then (yes) * :push service to\npea-data-storage; * endif * endif * endwhile (no) * :initialize a PEAModel object; * if (initialization successful?) then (yes) * :callback the PEAModel\nvia SuccessResponse; * else (no) * :callback error\nvia ErrorResponse; * endif * endif * stop * } * </uml> * * @param data - The data as CAEXFile. * @param pimadIdentifier - ??? * @param callback - ??? * @private */ convert(data, pimadIdentifier, callback) { // These variables will be continuously filled let communicationInterfaceData = []; // TODO > link to communication interface let dataAssemblies = []; let communicationSet = {}; let serviceSet = {}; let hmiSet = {}; let textSet = {}; let mtpPartResponseContent = {}; let servicePartResponseContent = []; let peaName = 'name: undefined'; let peaMetaModelRef = 'metaModelRef: undefined'; // looping through the first level instance hierarchy of the CAEX-File. data.InstanceHierarchy.forEach((instance) => { const localInternalElement = instance.InternalElement; if (instance.Name === 'ModuleTypePackage') { // store characteristic pea attributes peaName = localInternalElement.Name; peaMetaModelRef = localInternalElement.RefBaseSystemUnitPath; // get the CommunicationSet this.getSet('MTPSUCLib/CommunicationSet', localInternalElement.InternalElement, set => { communicationSet = set; }); this.getSet('MTPServiceSUCLib/ServiceSet', localInternalElement.InternalElement, set => { serviceSet = set; }); this.getSet('MTPHMISUCLib/HMISet', localInternalElement.InternalElement, set => { hmiSet = set; }); this.getSet('MTPTextSUCLib/TextSet', localInternalElement.InternalElement, set => { textSet = set; }); data.InstanceHierarchy.forEach((instance) => { if (Object.keys(serviceSet).length > 0) { if (serviceSet.ExternalInterface.Attribute.Value === instance.ID) { const serviceImporterPart = new ImporterPart_1.ServicePart(); serviceImporterPart.extract(instance, servicePartResponse => { servicePartResponseContent = servicePartResponse.getContent(); }); } } if (Object.keys(hmiSet).length > 0) { if (hmiSet.ExternalInterface.Attribute.Value === instance.ID) { const hmiImporterPart = new ImporterPart_1.HMIPart(); //TODO not implemented yet hmiImporterPart.extract(instance, hmiPartResponse => { hmiPartResponse.getContent(); }); } } if (Object.keys(textSet).length > 0) { if (textSet.ExternalInterface.Attribute.Value === instance.ID) { const textImporterPart = new ImporterPart_1.TextPart(); //TODO not implemented yet textImporterPart.extract(instance, textPartResponse => { textPartResponse.getContent(); }); } } }); const mtpImporterPart = new ImporterPart_1.MTPPart(); mtpImporterPart.extract({ CommunicationSet: communicationSet.InternalElement, HMISet: {}, ServiceSet: serviceSet.ExternalInterface, TextSet: {} }, mtpPartResponse => { if (mtpPartResponse.constructor.name === this.responseVendor.buySuccessResponse().constructor.name) { mtpPartResponseContent = mtpPartResponse.getContent(); communicationInterfaceData = mtpPartResponseContent.ServerCommunicationInterfaceData; dataAssemblies = mtpPartResponseContent.DataAssemblies; } else { Utils_1.logger.warn('Could not extract CommunicationSet'); } }); } }); // Checking the data for completeness if (JSON.stringify(mtpPartResponseContent) === JSON.stringify({}) || servicePartResponseContent.length === 0) { const localResponse = this.responseVendor.buyErrorResponse(); localResponse.initialize('Could not extract MTPSUCLib/CommunicationSet and/or MTPServiceSUCLib/ServiceSet. Aborting...', {}); callback(localResponse); } else { // data is fine -> Now merging Services and Procedures with DataAssemblies. const localServices = []; servicePartResponseContent.forEach((service) => { const localService = this.serviceFactory.create(); const localServiceDataAssembly = dataAssemblies.find(dataAssembly => { let testCondition = false; dataAssembly.getDataSourceIdentifier((response, identifier) => { testCondition = (service.DataAssembly.Value === identifier); }); return testCondition; }); if (localServiceDataAssembly === undefined) { Utils_1.logger.warn('Could not find referenced DataAssembly for service <' + service.Name + '> Skipping this service ...'); } else { const localServiceProcedures = []; // merging Procedures with DataAssemblies service.Procedures.forEach(procedure => { const localProcedureDataAssembly = dataAssemblies.find(dataAssembly => { let testCondition = false; dataAssembly.getDataSourceIdentifier((response, identifier) => { testCondition = (procedure.DataAssembly.Value === identifier); }); return testCondition; }); if (localProcedureDataAssembly === undefined) { Utils_1.logger.warn('Could not find referenced DataAssembly for procedure <' + service.Name + '> Skipping this procedure ...'); } else { const procedureAttributes = []; const procedureAttributeFactory = new ModuleAutomation_1.AttributeFactoryVendor().buyProcedureAttributeFactory(); procedure.Attributes.forEach(attribute => { const newProcedureAttribute = procedureAttributeFactory.create(); if (newProcedureAttribute.initialize({ DataType: attribute.AttributeDataType, Name: attribute.Name, Value: attribute.Value })) { procedureAttributes.push(newProcedureAttribute); } }); const localProcedure = this.procedureFactory.create(); if (localProcedure.initialize({ defaultValue: '', description: '', attributes: procedureAttributes, dataAssembly: localProcedureDataAssembly, dataSourceIdentifier: procedure.Identifier, metaModelRef: procedure.MetaModelRef, name: procedure.Name, parameter: procedure.Parameters, pimadIdentifier: 'TODO' })) { localServiceProcedures.push(localProcedure); } } }); const serviceAttributes = []; const serviceAttributeFactory = new ModuleAutomation_1.AttributeFactoryVendor().buyServiceAttributeFactory(); service.Attributes.forEach(attribute => { const newProcedureAttribute = serviceAttributeFactory.create(); if (newProcedureAttribute.initialize({ DataType: attribute.AttributeDataType, Name: attribute.Name, Value: attribute.Value })) { serviceAttributes.push(newProcedureAttribute); } }); // initialize the new service object ... if (localService.initialize({ defaultValue: '', description: '', attributes: serviceAttributes, dataAssembly: localServiceDataAssembly, dataSourceIdentifier: service.Identifier, metaModelRef: service.MetaModelRef, name: service.Name, parameter: service.Parameters, pimadIdentifier: uuid_1.v4(), procedure: localServiceProcedures })) { // ... and push it to the array. localServices.push(localService); } } }); this.cleanUpDataAssemblies(dataAssemblies); const localPEA = this.peaFactory.create(); // Initializing the local pea if (localPEA.initialize({ DataAssemblies: dataAssemblies, DataModel: peaMetaModelRef, DataModelVersion: new Backbone_1.BasicSemanticVersion(), FEAs: [], Name: peaName, PiMAdIdentifier: pimadIdentifier, Services: localServices, Endpoint: communicationInterfaceData })) { // successful -> callback with successful response const localSuccessResponse = this.responseVendor.buySuccessResponse(); localSuccessResponse.initialize('Success!', localPEA); callback(localSuccessResponse); } else { // error -> callback with error response const localErrorResponse = this.responseVendor.buyErrorResponse(); localErrorResponse.initialize('Could not extract PEAModel from ???. Aborting', {}); callback(localErrorResponse); } } } /** * remove HealthStateView(Procedures) and ServiceControls from dataAssemblies, because subscribeToAllVariables() * in backend will take this list for the variables * @param dataAssemblies * @private */ cleanUpDataAssemblies(dataAssemblies) { for (const dataAssembly of dataAssemblies) { // at first, get metaModelRef of dataAssembly let mMetaModelRef = ''; dataAssembly.getMetaModelRef((response, metaModelRef) => mMetaModelRef = metaModelRef); if (mMetaModelRef.includes('HealthStateView') || mMetaModelRef.includes('ServiceControl')) { // if DataAssembly is Procedure or Service, remove it! const index = dataAssemblies.indexOf(dataAssembly); dataAssemblies.splice(index, 1); // the dataAssemblies has changed-> start function again, but with updated list this.cleanUpDataAssemblies(dataAssemblies); break; } } } } exports.MTPFreeze202001Importer = MTPFreeze202001Importer; /* Factory */ class AImporterFactory { } class MTPFreeze202001ImporterFactory extends AImporterFactory { create() { const importer = new MTPFreeze202001Importer(); Utils_1.logger.debug(this.constructor.name + ' creates a ' + importer.constructor.name); return importer; } } exports.MTPFreeze202001ImporterFactory = MTPFreeze202001ImporterFactory; class LastChainElementImporterFactory extends AImporterFactory { create() { const importer = new LastChainLinkImporter(); Utils_1.logger.debug(this.constructor.name + ' creates a ' + importer.constructor.name); return importer; } } exports.LastChainElementImporterFactory = LastChainElementImporterFactory;