/*-------------------------------------------------------------------------------------------------------------- * Copyright (c) insite-gmbh. All rights reserved. * Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------------------------*/ 'use strict' export class PlcAbs { public Selector : string; public Type : string; public Offset : number; public Length: number; //currently serves for Bools and Strings public CountOfData :number; public Format : number; public Mnemonic : string; constructor(sel? :string, type? :string, offs? :number, len? :number, cod? :number, pointer :number = 0, mnemonic :string = 'DE') { } public static toINAX(absAddress :string, mnemonic :string = 'DE') :PlcAbs{ //converts given absolute PLC adress to INAX and returns element with set properties; set mnemonic to 'DE' for german operands absAddress = absAddress.replace(/ /g,''); let INAX = new PlcAbs; INAX.Mnemonic = mnemonic; if (absAddress.substr(0,2) === 'P#'){ if (absAddress.length < 8){ throw Error('address too short'); } //P#[Operand][Offset].[Length][Type][CountOfData] is at least 8 chars return INAX.splitPlcPointerAddress(absAddress, mnemonic); } else { if (absAddress.length < 3){ throw Error('address too short'); } //[Operand][Type][Offset] is at least 3 chars return INAX.splitPlcAddress(absAddress, mnemonic); } } public splitPlcAddress(absAddress :string, mnemonic :string = 'DE') :PlcAbs{ //splits a normal absAdress and returns an object with sproperties from the address this.Format = 0; let address = this.getINAXSelector(absAddress, mnemonic); //separates Type and Offset(_Length) let restAddress = this.extractINAXType(address); this.standardiseINAXType(); //gets Offset and Length from remaining Address part for Strings and Bools, parseInts remaining address to Offset for other types if (this.Type === 'S' || this.Type === 'X'){ this.getINAXNumericValues(restAddress); } else { this.Offset = parseInt(restAddress); } return this; } public splitPlcPointerAddress(absAddress :string, mnemonic :string = 'DE') :PlcAbs{ this.Format = 1; let address = this.getINAXSelector(absAddress.substring(2), mnemonic);//removes P# at the beginning and extracts the Selector if (address[0] === 'X'){ address = address.substring(1); } //removes X from pointer format address //separates Type and Offset(_Length) let restAddress = this.extractINAXType(address); this.standardiseINAXType(); this.getINAXNumericValues(restAddress); return this; } public extractINAXType(address :string, defaultType :string = 'X') :string{ //accepts address without selector, returns restAddress without Type let TYPES = [ 'X' , 'DATETIME' , 'TIMEBCD' , 'BOOLEAN' , 'BYTE' , 'BIT' , 'BOOL' , 'COUNT' , 'CHAR' , 'DWORD' , 'DATE' , 'DINT' , 'DT' , 'DI' , 'INT' , 'REAL' , 'S5TIME' , 'STRING' , 'TIMEOFDAY' , 'TIME' , 'TOD' , 'WORD' , 'CT' , 'DW' , 'C' , 'B' , 'T' , 'S' , 'I' , 'W' ]; if (address.search(/[^\d\.]/g) === -1){//if no Type is found, the defaultType is set this.Type = defaultType; return address; } let restAddress :string; let placeHolder = ''; if (this.Format === 1) { placeHolder = '.'; //in pointer format, replace Type with a dot to separate Length and CountOfData } for (let i = 0; i < TYPES.length; i++){ restAddress = address.replace(TYPES[i], placeHolder); if (restAddress.search(/[^\d\.]/g) === -1){ //If address without TYPES[i] only contains numbers and dots [\d\.] this.Type = TYPES[i]; return restAddress; } } throw Error('Invalid Type or illegal characters'); } public getINAXSelector(absAddress :string, mnemonic :string = 'DE') :string{ if (mnemonic === 'DE'){ switch (absAddress[0]) { case "D": let selector = absAddress.split('.')[0]; if (selector.substring(2).search(/\D/) !== -1){ throw Error('invalid DB selector'); } this.Selector = absAddress.split('.')[0]; return absAddress.substring(this.Selector.length+3); case "E": this.Selector = "IB"; return absAddress.substring(1); case "M": this.Selector = "FB"; return absAddress.substring(1); case "A": this.Selector = "QB"; return absAddress.substring(1); case "T": this.Selector = "TB"; return absAddress.substring(1); case "Z": this.Selector = "CT"; return absAddress.substring(1); default: throw Error('Invalid Selector'); } } else { switch (absAddress[0]) { case "D": this.Selector = absAddress.split('.')[0]; return absAddress.substring(this.Selector.length+3); case "I": this.Selector = "IB"; return absAddress.substring(1); case "M": this.Selector = "FB"; return absAddress.substring(1); case "Q": this.Selector = "QB"; return absAddress.substring(1); case "T": this.Selector = "TB"; return absAddress.substring(1); case "C": this.Selector = "CT"; return absAddress.substring(1); default: throw Error('Invalid Selector'); } } } /* public static getINAXAddress(absAddress :string) :string{ let offset = 0; if (absAddress[0] == 'D') { offset = absAddress.indexOf('.') + 3; } else { offset = 1; } let Address = absAddress.substring(offset); Address = Address.replace(/\./g,'_'); return Address; } */ public getINAXNumericValues(addressPart :string) :void{ //returns array with [ , ()] ( is only set for Strings or Bools respectively) let values = addressPart.split('.'); if (values[0] === ''){ throw Error('no Offset'); } if (values[1] === ''){ throw Error('no Length'); } this.Offset = parseInt(values[0]); this.Length = parseInt(values[1]); if(this.Length > 7){ throw Error('out of range: Length (Bitoffset)');} if (this.Format === 1){ //in pointer format, Type gets replaced by a dot in extractINAXType, so there is a third element in the array if (values[2] === undefined || values[2] === ''){ throw Error('no CountOfData');} this.CountOfData = parseInt(values[2]); } } public standardiseINAXType() :void{ //returns the shortest name for a Type if (this.Type.length < 3) { return; } //if Type consists of two characters at most, it already is in the shortest form possible switch(this.Type){ case 'BIT': case 'BOOL': this.Type = 'X'; break; case 'BYTE': this.Type = 'B'; break; case 'CHAR': this.Type = 'C'; break; case 'DATETIME': this.Type = 'DT'; break; case 'DINT': this.Type = 'DI'; break; case 'DWORD': this.Type = 'DW'; break; case 'INT': this.Type = 'I'; break; case 'REAL': this.Type = 'R'; break; case 'STRING': this.Type = 'S'; break; case 'TIME': this.Type = 'T'; break; case 'WORD': this.Type = 'W'; break; case 'COUNT': this.Type = 'CT'; break; } } public getAddress() :string{ if(this.Format) { //pointer format address return this.Type + this.Offset + '_' + this.Length; } else { //normal address if (this.Type === 'S' || this.Type === 'X'){ return this.Type + this.Offset + '_' + this.Length; } else { return this.Type + this.Offset; } } } public getSelector() :string{ return this.Selector; } public getType() :string{ return this.Type; } public getOffset() :number{ return this.Offset; } public getLength() :number{ return this.Length; } public getCountOfData() :number{ return this.CountOfData; } public getMnemonic() :string{ return this.Mnemonic; } public toPLC(mnemonic :string = this.Mnemonic) :string{ if (this.Format === 1){ return 'P# ' + this.getPLCSelector(mnemonic) + ' ' + this.Offset + '.' + this.Length + ' ' + this.Type + ' ' + this.CountOfData; } else { let address = ""; if (this.Type === 'S' || this.Type === 'X'){ address = this.Type + this.Offset + '.' + this.Length; } else { address = this.Type + this.Offset; } return this.getPLCSelector(mnemonic) + address; } } public getPLCSelector(mnemonic :string = this.Mnemonic) :string{ //pass 'DE' to output German operand types const DE = (mnemonic === 'DE'); switch(this.Selector){ case "IB": return(DE ? "E" : "I"); case "QB": return(DE ? "A" : "Q"); case "FB": return "M"; case "TM": return "T"; case "CT": return(DE ? "Z" : "C"); default: //case "DBxxxx" return this.Selector+".DB"; } } }