961 lines
28 KiB
TypeScript
961 lines
28 KiB
TypeScript
/**
|
|
* @author Martin Karkowski
|
|
* @email m.karkowski@zema.de
|
|
* @create date 2020-03-10 14:50:13
|
|
* @modify date 2020-06-03 14:04:26
|
|
* @desc [description]
|
|
*/
|
|
|
|
import * as handlebars from "handlebars";
|
|
|
|
import { AbstractParser } from "./default.parser";
|
|
import {
|
|
IParsedLogic,
|
|
IParseableTransitionDescription,
|
|
IParseableLogic,
|
|
IVariableDescription,
|
|
} from "../types/interface";
|
|
import {
|
|
deepClone,
|
|
rsetattr
|
|
} from "../../ZISS-TypeScript-Library/src/Object-Methods";
|
|
import { replaceAll, padString } from "../../ZISS-TypeScript-Library/src/String-Methods";
|
|
import { ILogicNetwork } from "../../ZISS-Network/type/ILogicNetwork";
|
|
import { IJsonSchema } from "../../ZISS-TypeScript-Library/src/JSON";
|
|
import { IArcExpression } from "../../ZISS-Petri-Net/types/IArcAnnotation";
|
|
import { IParseableInstance } from '../types/plc.additional.interfaces';
|
|
import { LOGIC_TEMPLATES } from './beckhoff.petri-net.templates';
|
|
|
|
import { v4 as uuidv4 } from 'uuid';
|
|
import { IEC_66113_convertValues, IEC_66113_ConvertType } from '../helpers/IEC66113_helpers';
|
|
import { dynamicSort } from '../../ZISS-TypeScript-Library/src/List-Methods';
|
|
|
|
interface IFunctionAnalyzerHelper {
|
|
pathDone: string,
|
|
doneRequired: boolean,
|
|
pathError: string,
|
|
errorRequired: boolean,
|
|
}
|
|
|
|
export interface IParseOptions {
|
|
nameOfPlc: string,
|
|
nameOfMainFb: string,
|
|
nameOfStatesType: string,
|
|
nameOfTransitionType: string,
|
|
nameOfUsedTransitionsVariable: string,
|
|
maxRecursion: number,
|
|
maxIterations: number,
|
|
additionalDelay: number,
|
|
}
|
|
|
|
export const defaultParseOptions: IParseOptions = {
|
|
nameOfPlc: 'plc-0001',
|
|
nameOfMainFb: 'FB_MAIN',
|
|
nameOfStatesType: 'STATES_DUT',
|
|
maxRecursion: 15,
|
|
maxIterations: 20,
|
|
nameOfTransitionType: 'TRANSITION_PARAMETERS_DUT',
|
|
nameOfUsedTransitionsVariable: 'TRANSTIONS_DUT',
|
|
additionalDelay: 20
|
|
};
|
|
|
|
export class BeckhoffParser extends AbstractParser {
|
|
protected _context: IParsedLogic;
|
|
protected _parsableContext: IParseableLogic;
|
|
|
|
public variableDefinition(context: IParsedLogic): string {
|
|
throw new Error("Method not implemented.");
|
|
}
|
|
|
|
/**
|
|
* Function which will generate Parameters for the desired Function
|
|
* @param task The Taskobject.
|
|
*/
|
|
protected _generateParameters(task: IParseableTransitionDescription) {
|
|
// Only Adapt a Copy
|
|
let ret = deepClone(task.params);
|
|
const pair = new Map<string, string>();
|
|
|
|
// Adapt the Parameters.
|
|
for (const updateDefinition of task.dynamicParams) {
|
|
pair.set(
|
|
updateDefinition.usedVariable,
|
|
'"' + updateDefinition.usedVariable + '"'
|
|
);
|
|
rsetattr(
|
|
ret,
|
|
updateDefinition.parameterPointer,
|
|
updateDefinition.usedVariable
|
|
);
|
|
}
|
|
|
|
let obj = JSON.stringify(ret, undefined, 4);
|
|
|
|
// Create The Text which will be used for the Parameters
|
|
for (const [key, value] of pair.entries()) {
|
|
replaceAll(obj, value, key);
|
|
}
|
|
|
|
return obj;
|
|
}
|
|
|
|
// Array, containing all Elements which should be replaced.
|
|
protected _replace: {
|
|
regex: RegExp;
|
|
replacer: string;
|
|
}[] = [
|
|
{
|
|
regex: /(?<=[ \]\)])or(?=[ \[\(])/g,
|
|
replacer: "OR"
|
|
},
|
|
{
|
|
regex: /(?<=[ \]\)])and(?=[ \[\(])/g,
|
|
replacer: "AND"
|
|
},
|
|
{
|
|
regex: /(?<=[ \]\)])!=(?=[ \[\(])/g,
|
|
replacer: "<>"
|
|
},
|
|
{
|
|
// (?<=[ \]\)])
|
|
regex: /not(?=[ \[\(])/g,
|
|
replacer: "NOT"
|
|
},
|
|
{
|
|
regex: /(?<=[ \]\)])\|(?=[ \[\(])/g,
|
|
replacer: "OR"
|
|
},
|
|
{
|
|
regex: /(?<=[ \]\)])\&(?=[ \[\(])/g,
|
|
replacer: "AND"
|
|
},
|
|
{
|
|
regex: /(?<=[ \]\)])true(?=[ \[\(])/g,
|
|
replacer: "TRUE"
|
|
},
|
|
{
|
|
regex: /(?<=[ \]\)])false(?=[ \[\(])/g,
|
|
replacer: "FALSE"
|
|
}
|
|
];
|
|
|
|
/**
|
|
* Function, which will be used to adapt the Path.
|
|
* @param value
|
|
*/
|
|
protected _adaptGuard(
|
|
guard: string,
|
|
simplifiedGuard: string,
|
|
OriginalScope: {
|
|
[index: string]: {
|
|
source: string;
|
|
schema: IJsonSchema;
|
|
};
|
|
},
|
|
simplifiedScope: {
|
|
[index: string]: {
|
|
source: string;
|
|
schema: IJsonSchema;
|
|
};
|
|
},
|
|
mappingOriginalToSimplified: Map<string, string>,
|
|
mappingSimplifiedToOriginal: Map<string, string>,
|
|
mappingSimplifiedToAdapted: Map<string, string>,
|
|
mappingAdaptedToSimplified: Map<string, string>,
|
|
mappingVariableToRoot: Map<string, string>
|
|
) {
|
|
let adaptedGuard = simplifiedGuard;
|
|
|
|
if (adaptedGuard === 'true'){
|
|
return 'TRUE';
|
|
} else if (adaptedGuard === 'false'){
|
|
return 'FALSE';
|
|
}
|
|
|
|
// Firstly get rid off the Elements (Chars, ot replace in the Equation)
|
|
for (const data of this._replace) {
|
|
adaptedGuard = adaptedGuard.replace(data.regex, data.replacer);
|
|
}
|
|
|
|
// Adapt the used Names
|
|
for (const [simple, adapted] of mappingSimplifiedToAdapted) {
|
|
adaptedGuard = replaceAll(adaptedGuard, simple, adapted);
|
|
}
|
|
|
|
return adaptedGuard;
|
|
}
|
|
|
|
/**
|
|
* Function, which will be used to adapt a Variable Name.
|
|
* @param value The original Variable Name
|
|
*/
|
|
protected _adaptVariableName(
|
|
variable: string,
|
|
simplifiedName: string,
|
|
path: string
|
|
) {
|
|
return simplifiedName;
|
|
}
|
|
|
|
public functionBlocks: {
|
|
[index: string]: {
|
|
|
|
// Defaultname (Type Name e.g. TON)
|
|
name: string,
|
|
|
|
// Variables used on the PLC Side.
|
|
plcInputs: {
|
|
name: string,
|
|
type: string
|
|
}[],
|
|
plcOutputs: {
|
|
name: string,
|
|
type: string
|
|
}[],
|
|
|
|
// Custom Flags for activating / deactivating the Block
|
|
start: string,
|
|
stop: string,
|
|
|
|
// Code Snippets for testing errors and success
|
|
error: string,
|
|
done: string,
|
|
|
|
// Mapping between the PLC inputs and outputs and system internal inputs / outputs.
|
|
inputMapping: {
|
|
// Index = Path of the Parameter, value = PLC-Input
|
|
[index: string]: string,
|
|
}
|
|
|
|
outputMapping: {
|
|
// Index = Path of the Parameter, value = PLC-Input
|
|
[index: string]: string,
|
|
},
|
|
|
|
// Optional Function to generate a custom Name.
|
|
generateInstanceName?: (
|
|
transition: IParseableTransitionDescription,
|
|
context: IParsedLogic,
|
|
handlebars: any,
|
|
name: string,
|
|
) => string
|
|
|
|
// Optional Template, which should be used for the Generation of the Data, during triggering
|
|
triggerTemplate?: string,
|
|
|
|
// Optional Template, which should be used for the Generation of the Code during the Main-Process
|
|
callTemplate?: string,
|
|
|
|
// Flag indicating, whe
|
|
requiresInstance: boolean,
|
|
|
|
deactivateOnDone: boolean
|
|
}
|
|
} = {
|
|
'timer.wait': {
|
|
name: 'TON',
|
|
done: '{{name}}{{{selector}}}Q',
|
|
error: 'FALSE',
|
|
inputMapping: {
|
|
value: '{{name}}{{{selector}}}PT := INT_TO_TIME({{value}});'
|
|
},
|
|
outputMapping: {},
|
|
plcInputs: [
|
|
{
|
|
name: 'IN',
|
|
type: 'BOOL'
|
|
},
|
|
{
|
|
name: 'PT',
|
|
type: 'TIME'
|
|
}
|
|
],
|
|
plcOutputs: [
|
|
{
|
|
name: 'Q',
|
|
type: 'BOOL'
|
|
},
|
|
{
|
|
name: 'ET',
|
|
type: 'TIME'
|
|
}
|
|
],
|
|
start: '{{name}}{{{selector}}}IN := TRUE;',
|
|
stop: '{{name}}{{{selector}}}IN := FALSE;',
|
|
requiresInstance: true,
|
|
deactivateOnDone: false
|
|
}
|
|
}
|
|
|
|
|
|
protected _defaultInstanceName(
|
|
transition: IParseableTransitionDescription,
|
|
context: IParsedLogic,
|
|
handlebars: any,
|
|
name: string,
|
|
) {
|
|
return 'FB_' + transition.id + '_' + name;
|
|
}
|
|
|
|
/**
|
|
* Helperfunction, which will generate a Context for a Function.
|
|
*
|
|
* @protected
|
|
* @param {IParseableTransitionDescription} transition
|
|
* @returns
|
|
* @memberof BeckhoffParser
|
|
*/
|
|
protected _getFunctionHelperContext<T>(transition: IParseableTransitionDescription) {
|
|
const _ret: IFunctionAnalyzerHelper = {
|
|
// Path to the Done Variable, if available.
|
|
pathDone: this._context.mappingSimplifiedToAdapted.get(
|
|
this._context.mapPathToSimplifiedName.get(
|
|
transition.id + ".function.output.done"
|
|
)
|
|
),
|
|
|
|
// Flag, indicating, whether a Done is requested
|
|
doneRequired: this._context.mapPathToSimplifiedName.has(
|
|
transition.id + ".function.output.done"
|
|
),
|
|
|
|
// Path to the Variable in which the corresponding error could be found.
|
|
pathError: this._context.mappingSimplifiedToAdapted.get(
|
|
this._context.mapPathToSimplifiedName.get(
|
|
transition.id + ".function.output.error"
|
|
)
|
|
),
|
|
|
|
// Flag, indicating, whether the Error of this Function is Checked or not.
|
|
errorRequired: this._context.mapPathToSimplifiedName.has(
|
|
transition.id + ".function.output.error"
|
|
)
|
|
}
|
|
|
|
|
|
return _ret as any as T & IFunctionAnalyzerHelper;
|
|
}
|
|
|
|
protected _timeAdaption(placeId: string) {
|
|
const _this = this;
|
|
|
|
|
|
// Get the Place matched to the ID
|
|
const place = this._context.places.filter(p => p.id === placeId)[0];
|
|
// Extract all involved Transitions.
|
|
|
|
const transitions: IParseableTransitionDescription[] = [
|
|
... place.consumedBy.map(transId => _this._parsableContext.transitions.filter(trans => (trans.id === transId))[0]),
|
|
... place.producedBy.map(transId => _this._parsableContext.transitions.filter(trans => (trans.id === transId))[0]),
|
|
... place.requiredBy.map(transId => _this._parsableContext.transitions.filter(trans => (trans.id === transId))[0]),
|
|
... place.avoidedBy.map(transId => _this._parsableContext.transitions.filter(trans => (trans.id === transId))[0])
|
|
];
|
|
|
|
const _context = {
|
|
transitions
|
|
}
|
|
|
|
// Create a Template to Test Execute the Function
|
|
const _template = `
|
|
{{#each transitions}}
|
|
{{methodName}}_ADAPT_TIMES(iCurrentTime);
|
|
{{/each}}
|
|
`
|
|
return handlebars.compile(_template)(_context);
|
|
}
|
|
|
|
/**
|
|
* List containing Functions. This can be used to create a custom code generation-Function
|
|
* per Function. Therefore the function will be linked by the index.
|
|
*
|
|
* @memberof BeckhoffParser
|
|
*/
|
|
public functions: {
|
|
[index: string]: {
|
|
generateFunctionTriggerCode: (
|
|
transition: IParseableTransitionDescription,
|
|
context: IParsedLogic,
|
|
handlebars: any
|
|
) => string,
|
|
generateInstanceStructure?: (
|
|
transition: IParseableTransitionDescription,
|
|
context: IParsedLogic,
|
|
handlebars: any
|
|
) => IParseableInstance
|
|
}
|
|
} = {
|
|
"plc.set": {
|
|
generateFunctionTriggerCode: (transition, context, handlebars) => {
|
|
// TODO: Update the Setters and Getters
|
|
// Mark the Task as Done.
|
|
|
|
// Test Object. Is used to update the Code
|
|
const _context = this._getFunctionHelperContext<{
|
|
entries: any[]
|
|
}>(transition);
|
|
|
|
_context.entries = [];
|
|
|
|
// Push all Ports to the Entries, which will be set.
|
|
for (const port in transition.params) {
|
|
_context.entries.push({
|
|
port: 'GVL_IOS.' + port,
|
|
value: IEC_66113_convertValues(transition.params[port])
|
|
});
|
|
}
|
|
|
|
// Although push all "dynamic" Variables to the Entries.
|
|
for (const param of transition.dynamicParams) {
|
|
_context.entries.push({
|
|
port: param.parameterPointer,
|
|
value: this._adaptVariableName("", param.usedVariable, "")
|
|
});
|
|
}
|
|
|
|
// Use a Template, which will add
|
|
const template = `
|
|
{{#if doneRequired}}
|
|
{{pathDone}} := FALSE;
|
|
{{/if}}
|
|
{{#if errorRequired}}
|
|
{{pathError}} := FALSE;
|
|
{{/if}}
|
|
|
|
{{#each entries}}
|
|
{{port}} := {{value}};
|
|
{{/each}}
|
|
|
|
{{#if doneRequired}}
|
|
{{pathDone}} := TRUE;
|
|
{{/if}}
|
|
{{#if errorRequired}}
|
|
{{pathError}} := FALSE;
|
|
{{/if}}
|
|
`;
|
|
|
|
return handlebars.compile(template)(_context);
|
|
},
|
|
},
|
|
"math.calculate": {
|
|
generateFunctionTriggerCode: (transition, context, handlebars) => {
|
|
return "calc";
|
|
},
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Function, which will be used to generate Function Code.
|
|
* @param taskData
|
|
*/
|
|
protected _getFunctionTriggerCode(
|
|
_transitionDescription: IParseableTransitionDescription
|
|
) {
|
|
const transition = deepClone(_transitionDescription);
|
|
|
|
if (transition.functionName && this.functions[transition.functionName]) {
|
|
const _func = this.functions[transition.functionName].generateFunctionTriggerCode;
|
|
return _func(transition, this._context, handlebars);
|
|
} else if (this.functionBlocks && this.functionBlocks[transition.functionName]) {
|
|
|
|
const _func = this.functionBlocks[transition.functionName];
|
|
|
|
// Generate the Name.
|
|
const _name = _func.generateInstanceName ? _func.generateInstanceName(transition, this._context, handlebars, _func.name) : this._defaultInstanceName(transition, this._context, handlebars, _func.name);
|
|
|
|
// Generate a Helper Context Element.
|
|
const _context = this._getFunctionHelperContext<{
|
|
entries: any[],
|
|
doneGetter: string,
|
|
errorGetter: string,
|
|
start: string,
|
|
startReset: string,
|
|
stop: string,
|
|
stopReset: string,
|
|
}>(transition);
|
|
|
|
_context.entries = [];
|
|
|
|
// Push all Ports to the Entries, which will be set.
|
|
for (const param in transition.params) {
|
|
if (_func.inputMapping[param]) {
|
|
_context.entries.push({
|
|
mapping: handlebars.compile(_func.inputMapping[param])({
|
|
value: transition.params[param],
|
|
name: _name,
|
|
selector: '_'
|
|
})
|
|
});
|
|
}
|
|
}
|
|
|
|
// Add all Dynamic Values to the Test.
|
|
for (const param of transition.dynamicParams) {
|
|
if (_func.inputMapping[param.parameterPointer]) {
|
|
_context.entries.push({
|
|
mapping: handlebars.compile(_func.inputMapping[param.parameterPointer])({
|
|
value: param.usedVariable,
|
|
name: _name,
|
|
selector: '_'
|
|
})
|
|
});
|
|
}
|
|
}
|
|
|
|
// Extend the Context.
|
|
_context.doneGetter = handlebars.compile(_func.done)({
|
|
name: _name,
|
|
selector: '.'
|
|
});
|
|
_context.errorGetter = handlebars.compile(_func.done)({
|
|
name: _name,
|
|
selector: '.'
|
|
});
|
|
_context.start = _func.start ? handlebars.compile(_func.start)({
|
|
name: _name,
|
|
selector: '_'
|
|
}) : '';
|
|
_context.startReset = _func.start ? handlebars.compile(_func.start)({
|
|
name: _name,
|
|
selector: '_'
|
|
}) : '';
|
|
_context.stop = _func.stop ? handlebars.compile(_func.stop)({
|
|
name: _name,
|
|
selector: '_'
|
|
}) : '';
|
|
_context.stopReset = _func.stop ? handlebars.compile(_func.stop)({
|
|
name: _name,
|
|
selector: '_'
|
|
}) : '';
|
|
|
|
// Use a Template, which will add
|
|
const template = _func.triggerTemplate || `
|
|
{{#if doneRequired}}
|
|
// Mark execution as Active
|
|
{{{pathDone}}} := FALSE;
|
|
{{/if}}
|
|
{{#if errorRequired}}
|
|
// Reset the Error
|
|
{{{pathError}}} := FALSE;
|
|
{{/if}}
|
|
|
|
// Stop the Block.
|
|
{{{stopReset}}}
|
|
updateInstances();
|
|
|
|
// Activate the Block
|
|
{{{start}}}
|
|
|
|
// Parameter assignment
|
|
{{#each entries}}
|
|
{{{mapping}}}
|
|
{{/each}}
|
|
updateInstances();
|
|
|
|
{{#if doneRequired}}
|
|
// Update the State of the Execution of the Block
|
|
{{{pathDone}}} := {{{doneGetter}}};
|
|
|
|
{{#if deactivateOnDone}}
|
|
// Deactivate the Function-Block:
|
|
IF {{{pathDone}}} THEN
|
|
{{{stop}}}
|
|
END_IF;
|
|
{{/if}}
|
|
{{/if}}
|
|
{{#if errorRequired}}
|
|
// Update the Error Flag
|
|
{{{pathError}}} := {{{errorGetter}}};
|
|
{{/if}}
|
|
`;
|
|
|
|
return handlebars.compile(template)(_context);
|
|
}
|
|
return "// Unkown Function. ==> Using remote Broker";
|
|
}
|
|
|
|
/**
|
|
* Function, which will be used to generate Function Code.
|
|
* @param taskData
|
|
*/
|
|
protected _getFunctionCallCode(
|
|
_transitionDescription: IParseableTransitionDescription
|
|
): IParseableInstance | null {
|
|
const transition = deepClone(_transitionDescription);
|
|
|
|
if (transition.functionName && this.functions[transition.functionName] && this.functions[transition.functionName].generateInstanceStructure) {
|
|
const _func = this.functions[transition.functionName].generateInstanceStructure;
|
|
return _func(transition, this._context, handlebars);
|
|
} else if (this.functionBlocks && this.functionBlocks[transition.functionName] && this.functionBlocks[transition.functionName].requiresInstance) {
|
|
|
|
const _func = this.functionBlocks[transition.functionName];
|
|
|
|
// Generate the Name.
|
|
const _name = _func.generateInstanceName ? _func.generateInstanceName(transition, this._context, handlebars, _func.name) : this._defaultInstanceName(transition, this._context, handlebars, _func.name);
|
|
|
|
// Generate a Helper Context Element.
|
|
const _context = this._getFunctionHelperContext<{
|
|
inputs: {
|
|
instanceVar: string,
|
|
accessor: string,
|
|
}[],
|
|
outputs: {
|
|
instanceVar: string,
|
|
accessor: string,
|
|
}[],
|
|
hasInputs: boolean,
|
|
hasOutputs: boolean,
|
|
stop: string,
|
|
doneGetter: string
|
|
}>(transition);
|
|
|
|
_context.hasInputs = _func.plcInputs ? _func.plcInputs.length > 0 : false;
|
|
_context.hasOutputs = _func.plcOutputs ? _func.plcOutputs.length > 0 : false;
|
|
_context.inputs = [];
|
|
_context.outputs = [];
|
|
|
|
// Extend the Context.
|
|
_context.doneGetter = handlebars.compile(_func.done)({
|
|
name: _name,
|
|
selector: '_'
|
|
});
|
|
_context.stop = _func.stop ? handlebars.compile(_func.stop)({
|
|
name: _name,
|
|
selector: '_'
|
|
}) : '';
|
|
|
|
for (const input of _func.plcInputs) {
|
|
_context.inputs.push({
|
|
accessor: _name + '_' + input.name,
|
|
instanceVar: _name + '.' + input.name
|
|
})
|
|
}
|
|
for (const output of _func.plcOutputs) {
|
|
_context.outputs.push({
|
|
accessor: _name + '_' + output.name,
|
|
instanceVar: _name + '.' + output.name
|
|
})
|
|
}
|
|
|
|
// Use a Template, which will add
|
|
const template = _func.callTemplate || (`
|
|
|
|
{{#if hasInputs}}
|
|
// Set the Inputs
|
|
{{#each inputs}}
|
|
{{instanceVar}} := {{accessor}};
|
|
{{/each}}
|
|
{{/if}}
|
|
|
|
{{#if hasOutputs}}
|
|
// Set the Outputs provided by the FB
|
|
{{#each outputs}}
|
|
{{accessor}} := {{instanceVar}};
|
|
{{/each}}
|
|
{{/if}}
|
|
|
|
{{#if doneRequired}}
|
|
// Update the State of the Execution of the Block
|
|
{{{pathDone}}} := {{{doneGetter}}};
|
|
|
|
{{#if deactivateOnDone}}
|
|
// Deactivate the Function-Block:
|
|
IF {{{pathDone}}} THEN
|
|
{{{stop}}}
|
|
END_IF;
|
|
{{/if}}
|
|
{{/if}}
|
|
|
|
{{#if errorRequired}}
|
|
// Update the Error Flag
|
|
{{{pathError}}} := {{{errorGetter}}};
|
|
{{/if}}
|
|
`);
|
|
|
|
const _ret: IParseableInstance = {
|
|
fBName: _func.name,
|
|
callCode: handlebars.compile(template)(_context),
|
|
inputs: _func.plcInputs.map(item => {
|
|
return {
|
|
name: _name + '_' + item.name,
|
|
type: item.type
|
|
}
|
|
}),
|
|
outputs: _func.plcOutputs.map(item => {
|
|
return {
|
|
name: _name + '_' + item.name,
|
|
type: item.type
|
|
}
|
|
}),
|
|
instanceName: _name,
|
|
transitionName: transition.id,
|
|
}
|
|
|
|
return _ret;
|
|
}
|
|
return null
|
|
}
|
|
|
|
protected _convertInput(definition: {
|
|
// Amount of Tokens that will be consumed (Result must be greater the minTokens)
|
|
consumed: IArcExpression;
|
|
placeId: string;
|
|
type:
|
|
| "expression"
|
|
| "inhibitor"
|
|
| "multiexpression"
|
|
| "test"
|
|
| "value"
|
|
| "variable";
|
|
minTokens: number;
|
|
}) {
|
|
return {
|
|
placeId: definition.placeId,
|
|
minTokens: definition.minTokens,
|
|
tokensToRemove: 1
|
|
};
|
|
}
|
|
|
|
protected _convertOutput(definition: {
|
|
// Amount of Tokens that will be produced
|
|
produced: IArcExpression;
|
|
placeId: string;
|
|
type:
|
|
| "expression"
|
|
| "inhibitor"
|
|
| "multiexpression"
|
|
| "test"
|
|
| "value"
|
|
| "variable";
|
|
maxTokens: number;
|
|
}): {
|
|
// Amount of Tokens that will be produced
|
|
placeId: string;
|
|
maxTokens: number;
|
|
tokensToAdd: number;
|
|
} {
|
|
return {
|
|
placeId: definition.placeId,
|
|
maxTokens: definition.maxTokens,
|
|
tokensToAdd: 1
|
|
};
|
|
}
|
|
|
|
protected _getAccessorOfGuardVariable(data: IVariableDescription){
|
|
|
|
// The Input of the Path, based on a Variable is defined as
|
|
// PLC-Name.[input/output].[module].[name]
|
|
// plc_0003.input.GVL_IOS.bInput24VMotorenIO
|
|
const splitted = data.path.split('.').slice(2);
|
|
|
|
return splitted.join('.');
|
|
}
|
|
|
|
protected _templates: {
|
|
[index: string]: string;
|
|
} = LOGIC_TEMPLATES;
|
|
|
|
/**
|
|
* Function, which will generate the MainLoop.
|
|
* @param network
|
|
*/
|
|
mainLoop(network: ILogicNetwork): string {
|
|
this.linkTemplates(network);
|
|
const render = handlebars.compile(this._templates.mainTemplate);
|
|
return render(this._parsableContext);
|
|
}
|
|
|
|
public linkTemplates(network: ILogicNetwork, ...args) {
|
|
super.linkTemplates();
|
|
|
|
// Reference to this object.
|
|
const _this = this;
|
|
|
|
// Register Functions
|
|
handlebars.registerHelper("getFunctionTriggerCode", (taskData) =>
|
|
_this._getFunctionTriggerCode(taskData)
|
|
);
|
|
// Register Functions
|
|
handlebars.registerHelper("uuid", () => '{' + uuidv4() + '}');
|
|
|
|
handlebars.registerHelper("getAccessorOfGuardVariable", (data) =>
|
|
_this._getAccessorOfGuardVariable(data)
|
|
);
|
|
|
|
handlebars.registerHelper("timeAdaption", (placeId) =>
|
|
_this._timeAdaption(placeId)
|
|
);
|
|
|
|
// Generate the Context
|
|
this._context = this.parseLogic(network);
|
|
this._parsableContext = this.getParseableLogic(network);
|
|
}
|
|
|
|
protected _parseLogic: IParsedLogic
|
|
|
|
/**
|
|
* Helper Function, to Generate the desired Logic.
|
|
* @param _network
|
|
*/
|
|
parseLogic(_network: ILogicNetwork) {
|
|
if (this._network != _network) {
|
|
const ret = super.parseLogic(_network);
|
|
|
|
for (const _var of ret.subscribedVariables){
|
|
_var.type = IEC_66113_ConvertType(_var.type) as any;
|
|
}
|
|
|
|
for (const _var of ret.variables){
|
|
_var.type = IEC_66113_ConvertType(_var.type) as any;
|
|
}
|
|
|
|
this._parseLogic = ret;
|
|
}
|
|
|
|
return this._parseLogic;
|
|
}
|
|
|
|
/**
|
|
* Function to create Project related Files.
|
|
*
|
|
* @param {ILogicNetwork} network The Network containing the Logic
|
|
* @param {IParseOptions} [options=defaultParseOptions] Additional Options for Parser
|
|
* @returns
|
|
* @memberof BeckhoffParser
|
|
*/
|
|
public createProjectFiles(network: ILogicNetwork, options: IParseOptions = defaultParseOptions) {
|
|
// Reference to the Own Object.
|
|
const _this = this;
|
|
|
|
// Internally the Templates
|
|
this.linkTemplates(network);
|
|
|
|
const codeFiles: {path: string, code: string}[] = [
|
|
{
|
|
path: 'POUs/'+options.nameOfMainFb + '.TcPOU',
|
|
code: this.generateMainFB(network,options),
|
|
},
|
|
{
|
|
path: 'DUTs/'+options.nameOfStatesType + '.TcDUT',
|
|
code: this.generateStatesType(network,options),
|
|
},
|
|
{
|
|
path: 'DUTs/'+options.nameOfTransitionType + '.TcDUT',
|
|
code: this.generateTransitionType(network,options),
|
|
},
|
|
{
|
|
path: 'DUTs/'+options.nameOfUsedTransitionsVariable + '.TcDUT',
|
|
code: this.generateTransitionsVariable(network,options),
|
|
}
|
|
];
|
|
|
|
// Array, cotaining all Files. These will be packed into a ZIP-File Later on.
|
|
const files: {path: string, code: string}[] = [
|
|
... codeFiles,
|
|
{
|
|
path: 'PlcTask.TcTTO',
|
|
code: handlebars.compile(this._templates.plcTask)(this.getParseableLogic(network))
|
|
},
|
|
{
|
|
path: 'Testcode.plcproj',
|
|
code: handlebars.compile(this._templates.plcProject)({codeFiles: codeFiles.map(item => {
|
|
return {
|
|
path: replaceAll(item.path, '/', '\\')
|
|
}
|
|
})})
|
|
}
|
|
]
|
|
|
|
return files;
|
|
}
|
|
|
|
protected _network: ILogicNetwork;
|
|
protected _options: IParseOptions
|
|
protected _getParseableLogic: IParseableLogic;
|
|
|
|
public getParseableLogic(network: ILogicNetwork, options: IParseOptions = defaultParseOptions){
|
|
|
|
if (this._network != network && this._options != options) {
|
|
|
|
this._network = network;
|
|
this._options = options;
|
|
|
|
const ret = super.getParseableLogic(network);
|
|
|
|
// Name of the Function Name.
|
|
(ret as any).options = options;
|
|
(ret as any).instances = [];
|
|
|
|
const _this = this;
|
|
const _adapt = (trans, length) => {
|
|
// Adding a Vaild Method Name.
|
|
(trans as any).methodName = 'T' + padString(trans.idx,length,true) + '_' +trans.id;
|
|
|
|
(trans as any).hasInputs = trans.consumed.length > 0;
|
|
(trans as any).hasOutputs = trans.produced.length > 0;
|
|
|
|
(trans as any).testOutputs = trans.produced.filter(place => place.maxTokens > 0).map(item => Object.assign(deepClone(options), item));
|
|
(trans as any).hasTestOutputs = (trans as any).testOutputs.length > 0;
|
|
|
|
(trans as any).hasTriggers = trans.triggers.length > 0;
|
|
|
|
(trans as any).hasLocks = trans.locks.length > 0;
|
|
|
|
(trans as any).hasReleases = trans.releases.length > 0;
|
|
|
|
// Transmit the Options
|
|
(trans as any).options = options;
|
|
for (const name of ['avoided', 'produced', 'testOutputs', 'consumed', 'required', 'triggers']){
|
|
trans[name] = trans[name].map(item => Object.assign({options},item));
|
|
}
|
|
|
|
// Test if Instance Code must be generated
|
|
if (trans.functionName && (
|
|
(_this.functions && _this.functions[trans.functionName] && _this.functions[trans.functionName].generateInstanceStructure) ||
|
|
(_this.functionBlocks && _this.functionBlocks[trans.functionName] && _this.functionBlocks[trans.functionName].requiresInstance)
|
|
)
|
|
) {
|
|
// Instance Code must be generated
|
|
(ret as any).instances.push(_this._getFunctionCallCode(trans));
|
|
}
|
|
}
|
|
|
|
for (const [idx,trans] of ret.transitions.entries()){
|
|
_adapt(trans,ret.transitions.length);
|
|
}
|
|
ret.transitionsSortedByPriority = ret.transitions.sort(dynamicSort('priority'))
|
|
for (const [idx,trans] of ret.actions.entries()){
|
|
_adapt(trans,ret.transitions.length);
|
|
}
|
|
|
|
// Transmit the Options
|
|
for (const name of ['places', 'initialMarking', 'subscribedVariables', 'transitions', 'variables']){
|
|
ret[name] = ret[name].map(item => Object.assign({options},item));
|
|
}
|
|
|
|
this._getParseableLogic = ret;
|
|
}
|
|
|
|
return this._getParseableLogic;
|
|
}
|
|
|
|
public generateMainFB(network: ILogicNetwork, options: IParseOptions = defaultParseOptions): string {
|
|
this.linkTemplates(network);
|
|
return handlebars.compile(this._templates.mainTemplate)(this.getParseableLogic(network, options));
|
|
}
|
|
|
|
public generateStatesType(network: ILogicNetwork, options: IParseOptions = defaultParseOptions): string {
|
|
this.linkTemplates(network);
|
|
return handlebars.compile(this._templates.statesStructTemplate)(this.getParseableLogic(network, options));
|
|
}
|
|
|
|
public generateTransitionsVariable(network: ILogicNetwork, options: IParseOptions = defaultParseOptions): string {
|
|
this.linkTemplates(network);
|
|
return handlebars.compile(this._templates.transitionVariableTemplate)(this.getParseableLogic(network, options));
|
|
}
|
|
|
|
public generateTransitionType(network: ILogicNetwork, options: IParseOptions = defaultParseOptions): string {
|
|
this.linkTemplates(network);
|
|
return handlebars.compile(this._templates.transitionStructTemplate)(this.getParseableLogic(network, options));
|
|
}
|
|
}
|