954 lines
27 KiB
TypeScript
954 lines
27 KiB
TypeScript
/**
|
|
* @author Martin Karkowski
|
|
* @email m.karkowski@zema.de
|
|
* @create date 2020-03-10 14:50:13
|
|
* @modify date 2020-11-25 13:37:27
|
|
* @desc [description]
|
|
*/
|
|
|
|
import * as handlebars from "handlebars";
|
|
import { v4 as uuidv4 } from "uuid";
|
|
import { deepClone, rsetattr } from "../../../lib/helpers/objectMethods";
|
|
import { padString, replaceAll } from "../../../lib/helpers/stringMethods";
|
|
import { ILogicNetwork } from "../../ZISS-Network/type/ILogicNetwork";
|
|
import { IArcExpression } from "../../ZISS-Petri-Net/types/IArcAnnotation";
|
|
import { IEC_66113_ConvertType, IEC_66113_convertValues } from "../helpers/IEC66113_helpers";
|
|
import {
|
|
IParseableLogic, IParseableTransitionDescription, IParsedLogic,
|
|
IVariableDescription
|
|
} from "../types/interface";
|
|
import { IParseableInstance } from "../types/plc.additional.interfaces";
|
|
import { LOGIC_TEMPLATES } from "./beckhoff.petri-net.templates";
|
|
import { AbstractParser } from "./default.parser";
|
|
|
|
|
|
|
|
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
|
|
const 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
|
|
);
|
|
}
|
|
|
|
const 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): 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): string {
|
|
|
|
// 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): void {
|
|
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): IParsedLogic {
|
|
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): IParseableLogic{
|
|
|
|
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));
|
|
}
|
|
}
|