@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
JavaScript
"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;