Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • silecs/opensilecs
  • k.fugiel/opensilecs
  • s.kupiecki/opensilecs
3 results
Show changes
Showing
with 764 additions and 1187 deletions
......@@ -14,37 +14,13 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import sys
import fesa.fesa_3_0_0.generateSourceCode
from test.testBase import *
def genHSource(className, silecsRoot, fesaRoot, sourcePath,logTopics):
return fesa.fesa_3_0_0.generateSourceCode.genHSource(className, silecsRoot, fesaRoot, sourcePath,logTopics)
import test.fesa.fillFESADeployUnitTest
import test.fesa.generateFesaDesignTest
import test.fesa.generateSourceCodeTest
import migration.runTests
def genCppSource(className, silecsRoot, fesaRoot, sourcePath,logTopics):
return fesa.fesa_3_0_0.generateSourceCode.genCppSource(className, silecsRoot, fesaRoot, sourcePath,logTopics)
import test.general.iecommonTest
import test.general.genplcsrcTest
import test.general.genParamTest
import test.general.genDuWrapperTest
def runTests():
migration.runTests.runAllTests()
test.fesa.fillFESADeployUnitTest.runTests()
test.fesa.generateFesaDesignTest.runTests()
test.fesa.generateSourceCodeTest.runTests()
test.general.iecommonTest.runTests()
test.general.genParamTest.runTests()
test.general.genplcsrcTest.runTests()
test.general.genDuWrapperTest.runTests()
print "################################################"
print "# Test suite finished - no failures detected ! #"
print "################################################"
sys.exit(0)
# ********************** module stand alone code **********************
if __name__ == "__main__":
runTests()
def genCppFiles(className, workspacePath, silecsDesignFilePath,logTopics={'errorlog': True}):
return fesa.fesa_3_0_0.generateSourceCode.genCppFiles(className, workspacePath, silecsDesignFilePath,logTopics)
......@@ -43,13 +43,13 @@ def genClassHeader(workspacePath, deploy, design, funcGetSilecsDesignFilePath, f
classDeclarations += genduwrappertemplate.getBlockClass(block,registerInitializerList,registerGetterSetter,registersDimentionsDeclaration,registersDeclaration)
blockGetters = ""
sendRecvBlocks = genduwrappertemplate.getDeviceSendRecvBlocks(designClass.getBlockNodes())
sendRecvBlocks = genduwrappertemplate.getDeviceSendRecvBlocks(designClass.getBlockNodes(), designClass.customTypesXmlNode)
for block in designClass.getDesignBlocks():
blockGetters += genduwrappertemplate.getDeviceBlockGetterSetter(block)
classDeclarations += genduwrappertemplate.getDeviceClass(blockGetters,sendRecvBlocks)
sendRecvBlocks = genduwrappertemplate.getControllerSendRecvBlocks(designClass.getBlockNodes())
sendRecvBlocks = genduwrappertemplate.getControllerSendRecvBlocks(designClass.getBlockNodes(), designClass.customTypesXmlNode)
classDeclarations = genduwrappertemplate.getDesignClass(design.name, design.version)
designWrapper = genduwrappertemplate.designFileTemplate.substitute({'designNameCapitalized' : iecommon.capitalizeString(design.name),'designNameUpper' : design.name.upper(),'classDeclarations' : classDeclarations})
......
......@@ -337,10 +337,10 @@ controllerSendTemplate = string.Template("""\
""")
def getControllerSendRecvBlocks(blockList):
def getControllerSendRecvBlocks(blockList, customTypesXmlNode):
text = ""
for blockNode in blockList:
block = DesignBlock(blockNode)
block = DesignBlock(blockNode, customTypesXmlNode)
map = {'blockName' : block.name,
'blockNameCapitalized' : block.getNameCapitalized()}
if block.isReadable():
......@@ -405,39 +405,39 @@ ${blockInitialization}\
matrixRegisterAssignementGetter = string.Template("""\
// Copy register ${regName}
${cType} *__${regName} = new ${cType}[block.${regName}Dim1 * block.${regName}Dim2];
getSilecsDevice()->getRegister("${regName}")->getVal${silecsTypeCapitalized}Array2D(__${regName}, block.${regName}Dim1, block.${regName}Dim2);
getSilecsDevice()->getRegister("${regName}")->getValArray2D<${cType}>(__${regName}, block.${regName}Dim1, block.${regName}Dim2);
block.set${regNameCapitalized}(__${regName});
delete[] __${regName};
""")
arrayRegisterAssignementGetter = string.Template("""\
// Copy register ${regName}
${cType} *__${regName} = new ${cType}[block.${regName}Dim1];
getSilecsDevice()->getRegister("${regName}")->getVal${silecsTypeCapitalized}Array(__${regName}, block.${regName}Dim1);
getSilecsDevice()->getRegister("${regName}")->getValArray<${cType}>(__${regName}, block.${regName}Dim1);
block.set${regNameCapitalized}(__${regName});
delete[] __${regName};
""")
scalarRegisterAssignementGetter = string.Template("""\
// Copy register ${regName}
block.set${regNameCapitalized}(getSilecsDevice()->getRegister("${regName}")->getVal${silecsTypeCapitalized}());
block.set${regNameCapitalized}(getSilecsDevice()->getRegister("${regName}")->getVal<${cType}>());
""")
matrixRegisterAssignementSetter = string.Template("""\
// Copy register ${regName}
${cType} *__${regName} = new ${cType}[block.${regName}Dim1 * block.${regName}Dim2];
block.get${regNameCapitalized}(__${regName});
getSilecsDevice()->getRegister("${regName}")->setVal${silecsTypeCapitalized}Array2D(__${regName}, block.${regName}Dim1, block.${regName}Dim2);
getSilecsDevice()->getRegister("${regName}")->setValArray2D<${cType}>(__${regName}, block.${regName}Dim1, block.${regName}Dim2);
delete[] __${regName};
""")
arrayRegisterAssignementSetter = string.Template("""\
// Copy register ${regName}
${cType} *__${regName} = new ${cType}[block.${regName}Dim1];
block.get${regNameCapitalized}(__${regName});
getSilecsDevice()->getRegister("${regName}")->setVal${silecsTypeCapitalized}Array(__${regName}, block.${regName}Dim1);
getSilecsDevice()->getRegister("${regName}")->setValArray<${cType}>(__${regName}, block.${regName}Dim1);
delete[] __${regName};
""")
scalarRegisterAssignementSetter = string.Template("""\
// Copy register ${regName}
getSilecsDevice()->getRegister("${regName}")->setVal${silecsTypeCapitalized}(block.get${regNameCapitalized}());
getSilecsDevice()->getRegister("${regName}")->setVal<${cType}>(block.get${regNameCapitalized}());
""")
def getDeviceBlockGetterSetter(block):
......@@ -447,7 +447,6 @@ def getDeviceBlockGetterSetter(block):
for register in block.getDesignRegisters():
map = {'regName' : register.name,
'regNameCapitalized' : register.getNameCapitalized(),
'silecsTypeCapitalized' : register.getSilecsTypeCapitalized(),
'cType' : register.getCType()}
if register.isArray2D():
blockInitialization += matrixRegisterAssignementGetter.substitute(map)
......@@ -465,7 +464,6 @@ def getDeviceBlockGetterSetter(block):
for register in block.getDesignRegisters():
map = {'regName' : register.name,
'regNameCapitalized' : register.getNameCapitalized(),
'silecsTypeCapitalized' : register.getSilecsTypeCapitalized(),
'cType' : register.getCType()}
if register.isArray2D():
blockInitialization += matrixRegisterAssignementSetter.substitute(map)
......@@ -502,10 +500,10 @@ deviceSendTemplate = string.Template("""\
""")
def getDeviceSendRecvBlocks(blockList):
def getDeviceSendRecvBlocks(blockList, customTypesXmlNode):
text = ""
for blockNode in blockList:
block = DesignBlock(blockNode)
block = DesignBlock(blockNode, customTypesXmlNode)
map = {'blockName' : block.name,
'blockNameCapitalized' : block.getNameCapitalized()}
if block.isReadable():
......
......@@ -19,7 +19,6 @@ import sys
import time
import zlib
import glob
import datetime
import shutil
import libxml2
......@@ -28,29 +27,17 @@ import xmltemplate
import iefiles
from iecommon import *
from Mapping import *
from model.Class.Register import *
from model.Class.Block import *
from model.Class.Class import *
from model.Deploy.Deploy import *
from model.Deploy.Controller import *
import model.Deploy.Device
from model.Param.Param import *
#-------------------------------------------------------------------------
# Global definitions
#-------------------------------------------------------------------------
msize = 0 # my regMemSize in the former PERL script
blkMemSize = 0 # my $blockMemSize = 0; in the former PERL script
blkAddr = 0 # my $blockAddress = 0; in the former PERL script
deviceMemSize = 0 # global memory-size of one class instance (sum of block-memory size)
nbBlock = 0 # number of block of the class
plcSize = 0
plcLast = 0
classMem = ""
instAddr = 0
blockCounter = 0 # used for NI block address generation
regCounter = 0 # used for NI register address generation
# maximum number of boolean bits in the same register address
MAX_BITS_IN_ADDRESS = 8
#-------------------------------------------------------------------------
# Trimming: remove new-line, tabulation and spaces of the string
......@@ -63,534 +50,40 @@ def trim (str):
str = str.replace('\n', '')
return str
#-------------------------------------------------------------------------
# Return the highest bit-alignment of the given address
def whichDataAlignment(value):
if (value % 2 != 0): return 8
if (value % 4 != 0): return 16
if (value % 8 != 0): return 32
return 64
#-------------------------------------------------------------------------
# DATA ALIGNMENT depends on different conditions: PLC brand, model and also
# depends on type of data structure (scalar or array)
# These methods are used to compute the correct address of each register respecting
# this conditions.
# Attention!! $addr is the relative address of the register within the block including
# this alignment while $msize is still the useful memory of the data which does not include
# memory spaces because of alignment shifts of the following variables if any.
# This remark is not true for the block mem-size which naturally includes the alignment spaces.
#
# Adjust the register address relying on the SCHNEIDER Premium/Quantum alignment constraints
# Unity Premium/Quantum is 16bits processor and base-address is interpreted as 16bit address
# Byte elements of array (including STRING) use 8bit alignment.
def alignPremiumRegAddress(addr, format, size, dim, dim2, strLen):
global msize
# internal string/unicode to integer cast
size = int(size)
addr = int(addr)
dim = int(dim)
dim2 = int(dim2)
strLen = int(strLen)
algnt = 16
while(whichDataAlignment(addr) < algnt):
addr+=1
msize = dim * dim2 * strLen * size #compute memory data size
if (format in ['int8', 'char']): #8bit signed type use word alignment (16bit)
msize = msize * 2 # while string and uint8-array use 8bit alignment
return addr
# Adjust the register address relying on the SCHNEIDER M340 alignment constraints
# Unity M340 is 32bits processor but base-address is interpreted as 16bit address (required even address in the mapping)
# 32bits alignment except for 8/16bits (16bits alignment)
# Byte elements of array (including STRING) use 8bit alignment.
def alignM340RegAddress(addr, format, size, dim, dim2, strLen):
global msize
# internal string/unicode to integer cast
size = int(size)
addr = int(addr)
dim = int(dim)
dim2 = int(dim2)
strLen = int(strLen)
algnt = 16
if (size > 2):
algnt = 32
while(whichDataAlignment(addr) < algnt):
addr+=1
msize = dim * dim2 * strLen * size #compute memory data size
if (format in ['int8', 'char']): #8bit signed type use word alignment (16bit)
msize = msize * 2 # while string and uint8-array use 8bit alignment
return addr
# Adjust the register address relying on the DIGI-Rabbit RCMx alignment constraints
# Use 16bits processor and base-address is interpreted as 16bit address
def alignDIGIRegAddress(addr, format, size, dim, dim2, strLen):
global msize
# internal string/unicode to integer cast
size = int(size)
addr = int(addr)
dim = int(dim)
dim2 = int(dim2)
strLen = int(strLen)
algnt = 16
while(whichDataAlignment(addr) < algnt):
addr+=1
msize = dim * dim2 * strLen * size #compute memory data size
if (format != 'string'):
if (size == 1): #but it's a 8bit type
msize = msize * 2 #so, it's a word alignment (only string use byte alignment)
return addr
def alignCNVRegAddress(addr, format, size, dim, dim2, strLen):
global regCounter
return regCounter
# Adjust the register relying on the SIEMENS Simatic alignment constraints
# 8bits alignment except for array and >8bits data (16bits alignment)
# In case of string, its length has to be +2 to hold info on string
def alignSimaticRegAddress(addr, format, size, dim, dim2, strLen):
global msize
# internal string/unicode to integer cast
size = int(size)
addr = int(addr)
dim = int(dim)
dim2 = int(dim2)
strLen = int(strLen)
algnt = 8
if ((size > 1) | (dim > 1) | (dim2 > 1) | (strLen > 1)):
algnt = 16
while(whichDataAlignment(addr) < algnt):
addr+=1
if strLen > 1: # register is a string,
if ((strLen % 2) != 0): strLen+=1 #adjusts the global size for byte type (word alignment)
strLen+=2 #add increment to strLen first two bytes for info on string (len, maxlen)
msize = dim * dim2 * strLen * size #compute memory data size
return addr
# Adjust the register address relying on the BECKHOFF Twincat BC9020 alignment constraints.
# TwinCAT BCxx PLCs are 16bits processor and base-address is interpreted as 16bit address
# 8bits alignment between elements of 8bit-data array (including strings).
# In case of string, its length has to be +1 to hold end-terminator.
def alignBCxxRegAddress(addr, format, size, dim, dim2, strLen):
global msize
# internal string/unicode to integer cast
size = int(size)
addr = int(addr)
dim = int(dim)
dim2 = int(dim2)
strLen = int(strLen)
algnt = 16
while(whichDataAlignment(addr) < algnt):
addr+=1
if strLen > 1: # register is a string,
strLen += 1 #TwinCAT requires '\0' string terminator.
msize = dim * dim2 * strLen * size #compute memory data size
return addr
# Adjust the register address relying on the BECKHOFF Twincat CX9020 alignment constraints.
# TwinCAT CXxx PLCs are 32bits processor but base-address is interpreted as 16bit address (required even address in the mapping)
# 32bits alignment except for 8/16bits (16bits alignment).
# 8bits alignment between elements of 8bit-data array (including strings).
# In case of string, its length has to be +1 to hold end-terminator.
def alignCXxxRegAddress(addr, format, size, dim, dim2, strLen):
global msize
# internal string/unicode to integer cast
size = int(size)
addr = int(addr)
dim = int(dim)
dim2 = int(dim2)
strLen = int(strLen)
algnt = 16
if (size > 2):
algnt = 32
while(whichDataAlignment(addr) < algnt):
addr+=1
if strLen > 1: # register is a string,
strLen += 1 #TwinCAT requires '\0' string terminator.
msize = dim * dim2 * strLen * size #compute memory data size
return addr
#block-address = block DB-Number
def computeSiemensBlockBlkAddress(regAddr, classAddr, nbDev):
#Compute block mem-size at first: each block has a dedicated DB-number which contains an array of devices
#each element of the array is a structure which must be 16bits aligned!
global blkMemSize
global plcModel
global blkAddr
# internal string/unicode to integer cast
regAddr = int(regAddr)
classAddr = int(classAddr)
nbDev = int(nbDev)
nextBlockAddr = regAddr
algnt = whichBaseAlignment[plcModel] #get the base struct-alignment of that PLC (16bit here)
# then adjust the next block-address by respecting this base-alignment.
while(int(whichDataAlignment(nextBlockAddr)) < int(algnt)):
nextBlockAddr+=1
blkMemSize = nextBlockAddr
# then compute the next block DB-number and return the current one
tmpblkAddr = blkAddr
blkAddr = blkAddr + 1
return tmpblkAddr + classAddr
#block-address = offset in the device memory
def computeSiemensDeviceBlkAddress(regAddr, classAddr, nbDev):
#Compute block mem-size at first: each device has a dedicated DB-number which contains a sequence of blocks
#each block is a structure which must be 16bits aligned!
global blkMemSize
global plcModel
global blkAddr
# internal string/unicode to integer cast
regAddr = int(regAddr)
classAddr = int(classAddr)
nbDev = int(nbDev)
nextBlockAddr = regAddr
algnt = whichBaseAlignment[plcModel] #get the base struct-alignment of that PLC (16bit here)
#then adjust the next block-address by respecting this base-alignment.
while(int(whichDataAlignment(nextBlockAddr)) < int(algnt)):
nextBlockAddr+=1
blkMemSize = nextBlockAddr
#then compute the next block DB-number and return the current one
tmpblkAddr = blkAddr
blkAddr = blkAddr + blkMemSize
return tmpblkAddr
# Block-address = absolute address in the PLC memory
def computeSchneiderBlockBlkAddress(regAddr, classAddr, nbDev):
global blkMemSize
global plcModel
global blkAddr
# Internal string/unicode to integer cast
regAddr = int(regAddr)
classAddr = int(classAddr)
nbDev = int(nbDev)
# Compute block mem-size at first:
# It corresponds to the next potential address that is 16bits or 32bits with SCHNEIDER.
nextBlockAddr = regAddr
# Each data-block for each class and device will be aligned on the worst-case alignment
# of the given PLC. This logic allows having a unique address computing whatever the mode (DEVICE/BLOCK)
# and the PLC model (16bits or 32bits).
algnt = whichBaseAlignment[plcModel] # get the "worst-case" base-alignment of that PLC
#then adjust the next block-address by respecting this base-alignment.
while(whichDataAlignment(nextBlockAddr) < int(algnt)):
nextBlockAddr+=1
blkMemSize = nextBlockAddr
# then compute the next block DB-number and return the current one
tmpblkAddr = blkAddr
# next absolute address: relies on the global class-address
blkAddr = blkAddr + (nbDev * blkMemSize)
return classAddr + tmpblkAddr
# Block-address = offset in the device memory
def computeSchneiderDeviceBlkAddress(regAddr, classAddr, nbDev):
global blkMemSize
global plcModel
global blkAddr
# internal string/unicode to integer cast
regAddr = int(regAddr)
classAddr = int(classAddr)
nbDev = int(nbDev)
#Compute block mem-size at first:
#it corresponds to the next potential address that is 16bits or 32bits with SCHNEIDER.
nextBlockAddr = regAddr
#Each data-block for each class and device will be aligned on the worst-case alignment
#of the given PLC. This logic allows having a unique adress computing whatever the mode (DEVICE/BLOCK)
#and the PLC model (16bits or 32bits).
algnt = whichBaseAlignment[plcModel] #get the "worst-case" base-alignment of that PLC
#then adjust the next block-address by respecting this base-alignment.
while(whichDataAlignment(nextBlockAddr) < int(algnt)):
nextBlockAddr+=1
blkMemSize = nextBlockAddr
#then compute the next block DB-number and return the current one
tmpblkAddr = blkAddr
#next relative address: independent from the global class-address
blkAddr = blkAddr + blkMemSize
return tmpblkAddr
def computeNiDeviceBlkAddress(regAddr, classAddr, nbDev):
global blockCounter
blockCounter = blockCounter + 1
return blockCounter
#-------------------------------------------------------------------------
# Compute the next register address relying on the data start-address and size.
def computeAnyNextRegAddress(brand, addr, dim, dim2=1):
global msize
# internal string/unicode to integer cast
addr = int(addr)
dim = int (dim)
dim2 = int(dim2)
addr = addr + msize; #compute the next address for any case
if (brand == 'SIEMENS'):
# SIEMENS requires specific treatment in case of array.
if ((dim > 1) | (dim2 > 1)):
#SIEMENS array is always followed by 16bits adressing
algnt = 16
while(whichDataAlignment(addr) < algnt):
addr+=1
elif (brand == 'NI'):
msize = 0
return addr
#-------------------------------------------------------------------------
def computeAnyBlockInstAddress(classAddr, devSize):
global instAddr
tmpInstAddr = instAddr
instAddr = instAddr + 1 #device-index
return tmpInstAddr
def computeSiemensDeviceInstAddress(classAddr, devSize):
global instAddr
tmpInstAddr = instAddr
instAddr = instAddr + 1 #device DB-number
return tmpInstAddr + classAddr
def computeSchneiderDeviceInstAddress(classAddr, devSize):
global instAddr
tmpInstAddr = instAddr
instAddr = instAddr + devSize #absolute address
return tmpInstAddr + classAddr
def computeNiDeviceInstAddress(classAddr, devSize):
return 0
#-------------------------------------------------------------------------
# Compute the base DB-number of the next class in the SIEMENS PLC memory
# BLOCK mode requires 1 DB per class block
def computeSiemensBlockNextBaseAddress(classAddr, nbBlk, nbDev, devSize):
global classMem
global plcLast
global plcSize
# internal string/unicode to integer cast
classAddr = int(classAddr)
nbBlk = int(nbBlk)
nbDev = int(nbDev)
devSize = int(devSize)
byteSize = nbDev * devSize
plcSize = int(plcSize) + int(byteSize) #global size of the plc configuration
startDB = classAddr #first DB used for this class
plcLast = classAddr + nbBlk - 1 #last DB used
classMem = "DB%d..DB%d / %d bytes"%(startDB,plcLast,byteSize)
return plcLast + 1 #next DB number
# Compute the base DB-number of the next instance in the SIEMENS PLC memory
# DEVICE mode requires 1 DB per class instance
def computeSiemensDeviceNextBaseAddress(classAddr, nbBlk, nbDev, devSize):
global classMem
global plcLast
global plcSize
classAddr = int(classAddr)
nbBlk = int(nbBlk)
nbDev = int(nbDev)
devSize = int(devSize)
byteSize = nbDev * devSize
plcSize = plcSize + byteSize #global size of the plc configuration
startDB = classAddr #first DB used for this class
plcLast = classAddr + nbDev - 1 #last DB used
classMem = "DB%d..DB%d / %d bytes"%(startDB,plcLast,byteSize)
return plcLast + 1 #next DB number
# Compute the base-address of the next class in the SCHNEIDER PLC memory
# DEVICE or BLOCK mode use the same memory size: next base address should be the same
# 'used-mem' info is expressed in words (/2) but address is computed in bytes
def computeSchneiderAnyNextBaseAddress(classAddr, nbBlk, nbDev, devSize):
global classMem
global plcLast
global plcSize
classAddr = int(classAddr)
nbBlk = int(nbBlk)
nbDev = int(nbDev)
devSize = int(devSize)
wordSize = (nbDev * devSize) / 2
plcSize = plcSize + wordSize #global size of the plc configuration
startAddr = classAddr / 2 #first word address used for this class
plcLast = startAddr + wordSize - 1 #LAST word address used for this class
classMem = "MW%d..MW%d / %d words"%(startAddr,plcLast,wordSize);
return (classAddr + (wordSize * 2)) #next word address expressed in bytes
def computeNiAnyNextBaseAddress(classAddr, nbBlk, nbDev, devSize):
return 0
def computeChecksumController( workspacePath, deploy, controller, silecsVersion, funcGetSilecsDesignFilePath, logTopics={'errorlog': True}):
majorSilecsVersion = iecommon.getMajorSilecsVersion(silecsVersion)
CRC32 = zlib.crc32(trim(str(majorSilecsVersion)),0)& 0xffffffff
CRC32 = zlib.crc32(trim(str(controller.baseAddress)),CRC32)& 0xffffffff
CRC32 = zlib.crc32(trim(str(majorSilecsVersion)).encode(),0)& 0xffffffff
CRC32 = zlib.crc32(trim(str(controller.address[MEM_TYPE])).encode(),CRC32)& 0xffffffff
for silecsDesign in deploy.silecsDesigns:
devices = controller.getDevicesOfDesign(silecsDesign.name)
if len(devices) > 0:
CRC32 = zlib.crc32(trim(silecsDesign.name),CRC32)& 0xffffffff
CRC32 = zlib.crc32(trim(silecsDesign.version),CRC32)& 0xffffffff
CRC32 = zlib.crc32(trim(silecsDesign.name).encode(),CRC32)& 0xffffffff
CRC32 = zlib.crc32(trim(silecsDesign.version).encode(),CRC32)& 0xffffffff
designDOM = iefiles.loadSilecsDesignDOM(workspacePath, silecsDesign, silecsVersion, funcGetSilecsDesignFilePath)
CRC32 = computeChecksumClass(designDOM,CRC32,logTopics)
for device in devices:
CRC32 = zlib.crc32(trim(device.silecsDeviceLabel),CRC32)& 0xffffffff
CRC32 = zlib.crc32(trim(device.silecsDeviceLabel).encode(),CRC32)& 0xffffffff
iecommon.logInfo("CRC32: %s" % str(CRC32),logTopics)
return CRC32
def computeChecksumClass(designDOM, CRC32, logTopics={'errorlog': True}):
designClass = DesignClass.getDesignClassFromRootNode(designDOM)
for block in designClass.getDesignBlocks():
CRC32 = zlib.crc32(trim(block.name),CRC32)& 0xffffffff
for block in designClass.getDesignMemoryBlocks():
CRC32 = zlib.crc32(trim(block.name).encode(),CRC32)& 0xffffffff
for register in block.getDesignRegisters():
CRC32 = zlib.crc32(trim(register.name),CRC32)& 0xffffffff
CRC32 = zlib.crc32(trim(register.format),CRC32)& 0xffffffff
CRC32 = zlib.crc32(trim(str(register.dim1)),CRC32)& 0xffffffff
CRC32 = zlib.crc32(trim(str(register.dim2)),CRC32)& 0xffffffff
CRC32 = zlib.crc32(trim(str(register.stringLength)),CRC32)& 0xffffffff
CRC32 = zlib.crc32(trim(register.name).encode(),CRC32)& 0xffffffff
CRC32 = zlib.crc32(trim(register.format).encode(),CRC32)& 0xffffffff
CRC32 = zlib.crc32(trim(str(register.dim1)).encode(),CRC32)& 0xffffffff
CRC32 = zlib.crc32(trim(str(register.dim2)).encode(),CRC32)& 0xffffffff
CRC32 = zlib.crc32(trim(str(register.stringLength)).encode(),CRC32)& 0xffffffff
return CRC32
# The following constant are used to align the address of a device block {set of registers}.
# With SCHNEIDER PLC, block is aligned depending on his 'worst-case' alignement {16bits or 32bits}.
# This logic allows having a unique adress computing whatever the mode {DEVICE/BLOCK} and the PLC model.
# Concerning SIEMENS PLC, block alignment should respect the 16bits alignement constraint for Struct&Array.
whichBaseAlignment = {
'SIMATIC_S7-300' : '16',
'SIMATIC_S7-400' : '16',
'SIMATIC_S7-1200' : '16',
'SIMATIC_S7-1500' : '16',
'SIMATIC_ET-200S' : '16',
'SIMATIC_S7-VIRTUAL': '16',
'Premium' : '16',
'Quantum' : '16',
'M340' : '32', # any data-block of M340 model will start on 32bits address
# even if the first register of the block is a 16bit data.
'Rabbit_RCM_4010' : '16',
'Rabbit_RCM_2000' : '16',
'BC9020' : '16',
'CX9020' : '32'
}
whichDataSize = {
'uint8' : '1',
'int8' : '1',
'uint16' : '2',
'int16' : '2',
'uint32' : '4',
'int32' : '4',
'float32' : '4',
'uint64' : '8',
'int64' : '8',
'float64' : '8',
'string' : '1',
'date' : '8',
# deprecated formats
'char' : '1',
'byte' : '1',
'word' : '2',
'dword' : '4',
'int' : '2',
'dint' : '4',
'real' : '4',
'dt' : '8'
}
whichRegAddressFunction = {
'SIMATIC_S7-300' : alignSimaticRegAddress,
'SIMATIC_S7-400' : alignSimaticRegAddress,
'SIMATIC_S7-1200' : alignSimaticRegAddress,
'SIMATIC_S7-1500' : alignSimaticRegAddress,
'SIMATIC_ET-200S' : alignSimaticRegAddress,
'SIMATIC_S7-VIRTUAL' : alignSimaticRegAddress,
'Premium' : alignPremiumRegAddress,
'Quantum' : alignPremiumRegAddress,
'M340' : alignM340RegAddress,
'Compact_RIO' : alignCNVRegAddress,
'PXI_RT' : alignCNVRegAddress,
'PXI_Windows' : alignCNVRegAddress,
'PC_Windows' : alignCNVRegAddress,
'Other_Support_CNV' : alignCNVRegAddress,
'Rabbit_RCM_4010' : alignDIGIRegAddress,
'Rabbit_RCM_2000' : alignDIGIRegAddress,
'BC9020' : alignBCxxRegAddress,
'CX9020' : alignCXxxRegAddress
}
whichBlkAddressFunction = {
'SIEMENS'+'BLOCK_MODE' : computeSiemensBlockBlkAddress,
'SIEMENS'+'DEVICE_MODE' : computeSiemensDeviceBlkAddress,
'SCHNEIDER'+'BLOCK_MODE' : computeSchneiderBlockBlkAddress,
'SCHNEIDER'+'DEVICE_MODE' : computeSchneiderDeviceBlkAddress,
'NI'+'DEVICE_MODE' : computeNiDeviceBlkAddress,
'DIGI'+'BLOCK_MODE' : computeSchneiderBlockBlkAddress,
'DIGI'+'DEVICE_MODE' : computeSchneiderDeviceBlkAddress,
'BECKHOFF'+'BLOCK_MODE' : computeSchneiderBlockBlkAddress
}
whichInstAddressFunction = {
'SIEMENS'+'BLOCK_MODE' : computeAnyBlockInstAddress,
'SIEMENS'+'DEVICE_MODE' : computeSiemensDeviceInstAddress,
'SCHNEIDER'+'BLOCK_MODE' : computeAnyBlockInstAddress,
'SCHNEIDER'+'DEVICE_MODE' : computeSchneiderDeviceInstAddress,
'NI'+'DEVICE_MODE' : computeNiDeviceInstAddress,
'DIGI'+'BLOCK_MODE' : computeAnyBlockInstAddress,
'DIGI'+'DEVICE_MODE' : computeSchneiderDeviceInstAddress,
'BECKHOFF'+'BLOCK_MODE' : computeAnyBlockInstAddress
}
whichBaseAddressFunction = {
'SIEMENS'+'BLOCK_MODE' : computeSiemensBlockNextBaseAddress,
'SIEMENS'+'DEVICE_MODE' : computeSiemensDeviceNextBaseAddress,
'SCHNEIDER'+'BLOCK_MODE' : computeSchneiderAnyNextBaseAddress,
'SCHNEIDER'+'DEVICE_MODE' : computeSchneiderAnyNextBaseAddress,
'NI'+'DEVICE_MODE' : computeNiAnyNextBaseAddress,
'DIGI'+'BLOCK_MODE' : computeSchneiderAnyNextBaseAddress,
'DIGI'+'DEVICE_MODE' : computeSchneiderAnyNextBaseAddress,
'BECKHOFF'+'BLOCK_MODE' : computeSchneiderAnyNextBaseAddress
}
# Needed to encapsulate "genParam" in order to allow unit-testing (fake all file interactions)
def genParamBase( funcGetSilecsDesignFilePath, funcGetParameterFile, funcGetSilecsDeployFilePath, funcGetParameterFileDirectory, workspacePath, deployName, deployVersion, silecsVersion, logTopics={'debuglog': True}):
# Global variable links
global plcModel, plcSize, plcLast
global PLCbaseAddress, checksumRef, owner, deviceMemSize, blkAddr, nbBlock, msize
global blkMemSize
global instAddr, classAddr
global classMem
global blockCounter # used for NI block address generation
global regCounter # used for NI register address generation
# Check the Deployment document exist for that PLC
deployPath = funcGetSilecsDeployFilePath(workspacePath, deployName)
if(not os.path.isfile(deployPath)):
iecommon.logError(deployName + "deployment file cannot be found in provided workspace",logTopics)
iecommon.logError(deployPath + ": '" + deployName + "' cannot be found in provided workspace '" + workspacePath + "'." ,True,logTopics)
deploy = Deploy.getDeployFromFile(deployPath)
......@@ -603,8 +96,10 @@ def genParamBase( funcGetSilecsDesignFilePath, funcGetParameterFile, funcGetSile
iecommon.logDebug("create directory %s" %paramPath,logTopics)
for controller in deploy.controllers:
classBaseAddress = controller.baseAddress
plcModel = controller.model
mappings = { MEM_TYPE: None, DI_TYPE: None, DO_TYPE: None, AI_TYPE: None, AO_TYPE: None }
for mappingType, mapping in mappings.items():
mappings[mappingType] = Mapping(controller.address[mappingType], mappingType)
# Messagges for debugging purpose
iecommon.logDebug("------ XML extracted informations ------",logTopics)
iecommon.logDebug("owner = " + deploy.owner,logTopics)
......@@ -612,47 +107,14 @@ def genParamBase( funcGetSilecsDesignFilePath, funcGetParameterFile, funcGetSile
iecommon.logDebug("plcModel = " + controller.model,logTopics)
iecommon.logDebug("plcBrand = " + controller.brand,logTopics)
iecommon.logDebug("plcProtocol = " + controller.protocol,logTopics)
iecommon.logDebug("PLCbaseAddress = %d" %controller.baseAddress,logTopics)
for mappingType, mapping in mappings.items():
iecommon.logDebug("address" + mappingType2String[mappingType] + " = " + str(controller.address[mappingType]),logTopics)
paramFile = funcGetParameterFile(workspacePath, deployName, controller.hostName )
iecommon.logInfo("Generate xml for parameters file: " + paramFile,logTopics)
# Create the parameter DOM for the output file
paramDOM = libxml2.newDoc(version='1.0')
outputRoot = libxml2.newNode('SILECS-Param')
outputRoot.setProp("silecs-version", silecsVersion)
paramDOM.addChild(outputRoot)
paramMappingInfoNode = libxml2.newNode("Mapping-Info")
outputRoot.addChild(paramMappingInfoNode)
paramOwnerNode = libxml2.newNode('Owner')
paramOwnerNode.setProp("user-login", deploy.owner)
paramMappingInfoNode.addChild(paramOwnerNode)
element2 = libxml2.newNode('Generation')
currentDate = str(datetime.datetime.now())
element2.setProp("date",currentDate)
paramMappingInfoNode.addChild(element2)
element2 = libxml2.newNode('Deployment')
CRC32 = computeChecksumController(workspacePath, deploy, controller, silecsVersion, funcGetSilecsDesignFilePath, logTopics)
element2.setProp("checksum", str(CRC32))
paramMappingInfoNode.addChild(element2)
#-------------------------------------------------------------------------
# Generate section <SILECS-Mapping></SILECS-Mapping>
#-------------------------------------------------------------------------
paramSilecsMappingNode = libxml2.newNode("SILECS-Mapping")
paramSilecsMappingNode.setProp("plc-name", controller.hostName)
paramSilecsMappingNode.setProp("plc-brand", controller.brand)
paramSilecsMappingNode.setProp("plc-system", controller.system)
paramSilecsMappingNode.setProp("plc-model", controller.model)
paramSilecsMappingNode.setProp("protocol", controller.protocol)
paramSilecsMappingNode.setProp("address", str(controller.baseAddress))
paramSilecsMappingNode.setProp("domain", controller.domain)
paramSilecsMappingNode.setProp("used-mem", "TODO")
outputRoot.addChild(paramSilecsMappingNode)
checksum = computeChecksumController(workspacePath, deploy, controller, silecsVersion, funcGetSilecsDesignFilePath, logTopics)
param = Param.createParam(silecsVersion, deploy.owner, checksum)
param.addController(controller)
for deployDesign in deploy.silecsDesigns:
devices = controller.getDevicesOfDesign(deployDesign.name)
......@@ -664,26 +126,32 @@ def genParamBase( funcGetSilecsDesignFilePath, funcGetParameterFile, funcGetSile
iecommon.logDebug("-----------------------------------------",logTopics)
iecommon.logDebug("------ Analysing Class " + deployDesign.name + " ------",logTopics)
iecommon.logDebug("-----------------------------------------",logTopics)
designDOM = iefiles.loadSilecsDesignDOM(workspacePath, deployDesign, silecsVersion, funcGetSilecsDesignFilePath)
if iecommon.supportsDateTimeLong(controller):
datetime_format = xmltemplate.DATE_TIME_LONG
else:
datetime_format = xmltemplate.DATE_AND_TIME
iecommon.logDebug("Datetime format is:", datetime_format)
designDOM = iefiles.loadSilecsDesignDOM(workspacePath, deployDesign, silecsVersion, funcGetSilecsDesignFilePath, datetime_format)
designClass = DesignClass.getDesignClassFromRootNode(designDOM)
paramClass = ParamClass()
paramClass.initWithDesignClass(designClass)
paramSilecsMappingNode.addChild(paramClass.xmlNode)
#-------------------------------------------------------------------------
# Generate section <Block></Block>
#-------------------------------------------------------------------------
blockCounter = 0
deviceMemSize = 0 # global memory-size of one class instance (sum of block-memory size)
blkAddr = 0 # memory address of the block (using byte addressing)
nbBlock = 0 # number of block of the class
param.controller.addParamClass(paramClass)
controller.blockCounter = 0
for mappingType, mapping in mappings.items():
mapping.blockAddress = 0
mapping.nbBlock = 0
mapping.deviceDataSize = 0
mapping.clearInstanceAddressIfDefined()
computeBlkAddress = whichBlkAddressFunction[controller.brand + controller.protocol]
# INNER LOOP TO ACCESS AT BLOCK LEVEL
for designBlock in designClass.getDesignBlocks():
# Siemens does not support to read back IO-Setting registers TODO: What about other PLC types ?
if controller.brand == "SIEMENS" and designBlock.isIOBlock() and designBlock.isSetting():
raise Exception("Error: Siemens PLC's do not support to read back IO-Settings. Please use 'Command-IO-Block' instead of 'Setting-IO-Block' in the silecsdesign")
paramBlock = ParamBlock()
paramBlock.initWithDesignBlock(designBlock)
......@@ -692,74 +160,118 @@ def genParamBase( funcGetSilecsDesignFilePath, funcGetParameterFile, funcGetSile
regAddress = 0 # memory address of the register (using byte addressing)
blockSize = 0 # block size (sum of the register size)
blockMemSize = 0 # block size (sum of the regist)
instAddr = 0 # initial class address BUG FIXED
regCounter = 0 # used for NI register address generation
alignRegAddress = whichRegAddressFunction [controller.model]
#-------------------------------------------------------------------------
# Generate section <Register></Register>
#-------------------------------------------------------------------------
controller.regCounter = 0 # used for NI register address generation
# additional data for managing booleans
isBooleanRegister = False
bitNumber = 0
nextRegister = None
# INNER LOOP TO ACCESS AT REGISTER LEVEL
for designRegister in designBlock.getDesignRegisters():
designRegisters = designBlock.getDesignRegisters()
for i, designRegister in enumerate(designRegisters):
if i + 1 != len(designRegisters):
nextRegister = designRegisters[i+1]
else:
nextRegister = None
iecommon.logDebug("------ Processing Register " + designRegister.name + " ------",logTopics)
# Allow bool register only for Siemens controller
if designRegister.format == 'bool':
isBooleanRegister = True
if not designRegister.isScalar():
iecommon.logError("Invalid format in register: %s Bool format available only for scalars" %designRegister.name, False, logTopics)
sys.exit(2)
if controller.brand != 'SIEMENS':
iecommon.logError("Invalid format in register: %s. Bool format available only on SIEMENS controllers" %designRegister.name, False,logTopics)
sys.exit(2)
else:
isBooleanRegister = False
bitNumber = 0
# Set length attribute only for string registers
if designRegister.format == 'string':
if controller.brand == 'RABBIT':
if designRegister.format == 'string': # TODO: Port this constraint to java
if controller.brand == 'DIGI':
# RABBIT has 8bit memory alignment but uses 16bit word communication protocol (MODBUS).
# String buffer (8bit elements) must have even number of bytes.
if ((int(designRegister.dim1)*int(designRegister.dim2)*int(designRegister.stringLength)) % 2 == 1): #XML CliX cannot check that constraint!
iecommon.logError("String length of %s designRegister must be an even value." %designRegister.name, False,logTopics)
sys.exit(2)
# Set register size
regSize = whichDataSize[designRegister.format]
# Set register address
regAddress = alignRegAddress(regAddress, designRegister.format, regSize, designRegister.dim1, designRegister.dim2, designRegister.stringLength)
regCounter = regCounter + 1 # used for NI register address generation
regAddress = controller.alignRegAddress(designBlock, designRegister, regAddress)
controller.regCounter += 1 # used for NI register address generation
# Set register mem-size
paramRegister = ParamRegister()
paramRegister.initWithDesignRegister(designRegister,regSize,regAddress,msize)
# Compute address for the next register
regAddress = computeAnyNextRegAddress(controller.brand, regAddress, designRegister.dim1, designRegister.dim2)
if isBooleanRegister:
paramRegister.initWithDesignRegister(designRegister,regAddress,controller.msize, bitNumber)
else:
paramRegister.initWithDesignRegister(designRegister,regAddress,controller.msize)
if isBooleanRegister and nextRegister and nextRegister.format == "bool":
bitNumber = (bitNumber + 1) % MAX_BITS_IN_ADDRESS
if bitNumber == 0:
# Compute address for the next register if max bits in address achieved
regAddress = controller.computeNextRegAddress(designBlock, designRegister, regAddress)
else:
# Compute address for the next register
regAddress = controller.computeNextRegAddress(designBlock, designRegister, regAddress)
paramBlock.xmlNode.addChild(paramRegister.xmlNode)
#paramRegister.xmlNode.shellPrintNode()
#iterativelly compute the block size (accumulator initialized outside the loop)
blockSize = blockSize + (int(regSize) * designRegister.dim1 * designRegister.dim2)
blockSize = blockSize + (paramRegister.size * paramRegister.dim1 * paramRegister.dim2)
# END OF INNER LOOP TO ACCESS AT REGISTER LEVEL
paramBlock.setSize(blockSize)
paramBlock.setAddress(computeBlkAddress(regAddress, int(classBaseAddress),nbDevice))
paramBlock.setMemSize(blkMemSize)
# Append block
blockSpecificMapping = mappings[paramBlock.type]
paramBlock.setAddress(controller.computeBlkAddress(paramBlock,blockSpecificMapping, regAddress,nbDevice))
paramBlock.setMemSize(blockSpecificMapping.blockDataSize)
paramClass.xmlNode.addChild(paramBlock.xmlNode)
# Count the number of devices
nbBlock = nbBlock+1
# Accumulate blkMemSize to compute the total deviceMemSize
deviceMemSize = deviceMemSize + blkMemSize
# END OF INNER LOOP TO ACCESS AT BLOCK LEVEL (LOOP-2)
# Set block Address
paramClass.setAddress(classBaseAddress)
for mappingType, mapping in mappings.items():
paramClass.setAddress(mapping.classBaseAddress,mappingType)
computeInstAddress = whichInstAddressFunction[ controller.brand + controller.protocol ]
for device in devices:
instance = libxml2.newNode("Instance")
instance.setProp("label", device.silecsDeviceLabel)
instance.setProp("address", str(computeInstAddress(classBaseAddress, deviceMemSize)))
instance.setProp("fesa-label", device.fesaDeviceName)
instance.setProp("address", str(int(controller.computeInstAddress(mappings[MEM_TYPE]))))
instance.setProp("DI-address", str(int(controller.computeInstAddress(mappings[DI_TYPE]))))
instance.setProp("DO-address", str(int(controller.computeInstAddress(mappings[DO_TYPE]))))
instance.setProp("AI-address", str(int(controller.computeInstAddress(mappings[AI_TYPE]))))
instance.setProp("AO-address", str(int(controller.computeInstAddress(mappings[AO_TYPE]))))
paramClass.xmlNode.addChild(instance)
# Compute the memory address for the next class
computeBaseAddress = whichBaseAddressFunction [controller.brand + controller.protocol]
classBaseAddress = computeBaseAddress(classBaseAddress, nbBlock, nbDevice, deviceMemSize);
for mappingType, mapping in mappings.items():
controller.computeNextBaseAddress(mapping, nbDevice);
# Set class used-memory
paramClass.setUsedMemory(classMem)
iecommon.logInfo("Used-memory for Class " + deployDesign.name + ": "+str(classMem),logTopics)
for mappingType, mapping in mappings.items():
paramClass.setUsedData(mapping.usedData,mappingType)
iecommon.logInfo("Used-"+ mappingType2String[mappingType] +" for Class " + deployDesign.name + ": " + mapping.usedData ,logTopics)
#Display details about the PLC memory using for the global configuration
if (controller.brand == 'SIEMENS'):
# SIEMENS works with data-block addressing
plcUsedMem = "DB"+str(controller.address[MEM_TYPE])+"..DB"+str(controller.memLast)+" / "+str(controller.memSize)+" bytes"
else:
# SCHNEIDER works with absolute addressing
startAddr = int(controller.address[MEM_TYPE] /2) #Memory info uses word-addressing
plcUsedMem = "MW"+str(startAddr)+"..MW"+str(controller.memLast)+" / "+str(controller.memSize)+" words"
# print plc used memory
iecommon.logInfo("Used-memory for PLC " + deploy.name + ": "+str(plcUsedMem),logTopics)
#------------------------------------------------------
# Generate output XML
#------------------------------------------------------
iefiles.saveXMLToFile(paramFile,paramDOM)
iefiles.saveXMLToFile(paramFile,param.xmlNode.get_doc())
iecommon.logDebug("---------------------------------------------------------",logTopics)
iecommon.logDebug("------GENERATED FILE: %s "%paramFile,logTopics)
......
......@@ -18,15 +18,19 @@ import sys
import time
import zlib
import libxml2
import datetime
import string
import iecommon
import iefiles
import xmltemplate
import s7template
import virtualS7Template
import rabbitTemplate
import DIGITemplate
from iecommon import *
from model.Param.Param import *
from model.Param.Controller import *
from model.Class.Register import ParamRegister
from model.Class.Block import ParamBlock
from model.Class.Class import ParamClass
......@@ -40,17 +44,6 @@ from model.Deploy.Deploy import *
#This Python-script generates PLC sources document from XML Parameter documents
#which are supposed to be well-formed (generated with the 'genparam' script).
# Deploy global information
class DeploymentInfo(object):
plcModel = None # S7-300, .., Premium, ..
plcSystem = None # UNITY, STEP-7, TIA-PORTAL, ...
plcProtocol = None # DEVICE_MODE or BLOCK_MODE
address = None
checksum = None # checksum of the related parameter file
owner = None # onwer of the Deployment document
silecsVersion = None
whichUnityFormat = {
'uint8' : 'BYTE',
'int8' : 'WORD',
......@@ -71,7 +64,8 @@ whichUnityFormat = {
'int' : 'INT',
'dint' : 'DINT',
'real' : 'REAL',
'dt' : 'DT'
'dt' : 'DT',
'dtl' : 'DTL'
}
whichTwincatFormat = {
......@@ -83,9 +77,9 @@ whichTwincatFormat = {
'int32' : 'DINT',
'float32' : 'REAL',
'string' : 'STRING',
# 'uint64' not supported by PLCs
# 'int64' not supported by PLCs
# 'float64' not supported by PLCs
'uint64' : 'ULINT',
'int64' : 'LINT',
'float64' : 'LREAL',
'date' : 'DT',
'char' : 'SINT',
'byte' : 'BYTE',
......@@ -98,8 +92,8 @@ whichTwincatFormat = {
'string' : 'STRING'
}
schneiderRegArray = """ARRAY[0..%d] OF %s"""
schneiderRegArray2d = """ARRAY[0..%d, 0..%d] OF %s"""
schneiderRegArray = string.Template("""ARRAY[0..${value}] OF ${len}""")
schneiderRegArray2d = string.Template("""ARRAY[0..${value1}, 0..${value2}] OF ${len}""")
# Beckhoff templates (using newlines \r\n because TwinCAT compiler complains with line feed character)
beckhoffReg = """ %s_%04x_%s AT %%MW%s: %s;\r\n\r\n"""
......@@ -121,103 +115,111 @@ def xsyRegister(register):
if register.isString():
return strLen
elif register.isStringArray():
return schneiderRegArray %(register.dim1-1, strLen)
return schneiderRegArray.substitute(value=register.dim1-1, len=strLen)
else: #2D-Array
return schneiderRegArray2d %(register.dim1-1, register.dim2-1, strLen)
return schneiderRegArray2d.substitute(value1=register.dim1-1, value2=register.dim2-1, len=strLen)
else:
if register.isScalar():
return whichUnityFormat[register.format]
elif register.isArray():
return schneiderRegArray %(register.dim1-1, whichUnityFormat[register.format])
return schneiderRegArray.substitute(value=register.dim1-1, len=whichUnityFormat[register.format])
else: #2D-Array
return schneiderRegArray2d %(register.dim1-1, register.dim2-1, whichUnityFormat[register.format])
return schneiderRegArray2d.substitute(value1=register.dim1-1, value2=register.dim2-1, len=whichUnityFormat[register.format])
def getDateTime():
dt = time.localtime(time.time())
return "DT#%s-%s-%s-%s:%s:%s" %(dt[0],dt[1],dt[2],dt[3],dt[4],dt[5])
def decimalToBCD(value):
return ((value//10)*16)+(value%10)
# convert date to DATE_TIME_LONG or DATE_AND_TIME string format (Siemens SIMATIC PLC)
def getDateTime(controller):
dt = datetime.datetime.now()
if iecommon.supportsDateTimeLong(controller):
return "DTL#%s-%s-%s-%s:%s:%s.%03d" %(dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, dt.microsecond // 1000)
else:
return "DT#%s-%s-%s-%s:%s:%s" % (dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second)
#-------------------------------------------------------------------------
# SIEMENS PLC code generation
#-------------------------------------------------------------------------
def generateSiemensSources(paramDOM, sourceFolderPath ,logTopics):
stlString = ''
def generateSiemensSources(param, sourceFolderPath ,logTopics):
stlString = s7template.head.substitute()
symString = '' # for .sdf Symbol file
deploy = getDeploymentInformation(paramDOM)
# Prepare the Simatic Symbols (<.sdf> file)
DBnumber = long(deploy.address) #first data-block (SilecsHeader) starts from the base-address
UDTnumber = long(deploy.address) #use base data-block address for UDT number as well (to be reserved by the developer)
DBnumber = param.controller.address[MEM_TYPE] #first data-block (SilecsHeader) starts from the base-address
UDTnumber = param.controller.address[MEM_TYPE] #use base data-block address for UDT number as well (to be reserved by the developer)
# Generate sources for each class that is deployed in the PLC
for paramClass in ParamClass.getParamClassesFromRootNode(paramDOM):
for paramClass in param.controller.paramClasses:
deviceDOMList = paramClass.getDeviceInstanceNodes() # get Device instances of that class
deviceNumber = len(deviceDOMList)
# Generate the Blocks definition
for blockIndex, block in enumerate(paramClass.getParamBlocks()):
for blockIndex, block in enumerate(paramClass.getParamMemoryBlocks()):
registerList = ''
for register in block.getParamRegisters():
# If PLC does not supports this register format abort this generation and log the error
if(register.format in ['uint64','int64','float64']):
iecommon.logError('ERROR: In design %s_%s register %s, %s format not supported for current controller model.'
%(paramClass.name, paramClass.version, register.name, register.format)
, True,logTopics)
#FLO: DO NOT LOG ERROR IF TWINCAT3 !!
# If PLC does not supports this register format abort this generation and log the error
# if(registerFormat in ['uint64','int64','float64']):
#iecommon.logGenerationResult(iecommon.opts.username,'ERROR: In register '+registerName+', '+registerFormat+' format not supported for current controller model.')
#iecommon.logError('ERROR: In register '+registerName+', '+registerFormat+' format not supported for current controller model.', True)
#create register value if required (diagnostic registers assignment in particular)
registerValue = s7template.stlRegisterValue(register.name, deploy.owner, getDateTime(), deploy.checksum,deploy.silecsVersion)
registerValue = s7template.stlRegisterValue(register.name, param.owner, getDateTime(param.controller), param.checksum,param.silecsVersion)
if register.format == 'string':
registerList += s7template.stlRegister(register.name, register.format, register.dim1, register.dim2, registerValue, long(register.stringLength))
registerList += s7template.stlRegister(register.name, register.format, register.dim1, register.dim2, registerValue, int(register.stringLength))
else:
registerList += s7template.stlRegister(register.name, register.format, register.dim1, register.dim2, registerValue)
stlString += s7template.stlBlockUDT(deploy.owner, paramClass.name, paramClass.version, block.name, registerList, (blockIndex == 0))
stlString += s7template.stlBlockUDT(param.owner, paramClass.name, paramClass.version, block.name, registerList, (blockIndex == 0))
symString += s7template.symBlockUDT(paramClass.name, paramClass.version, block.name, UDTnumber, (blockIndex == 0))
UDTnumber += 1
if deploy.plcProtocol == 'DEVICE_MODE':
if param.controller.protocol == 'DEVICE_MODE':
# Generate the Data-Blocks: one DB per Device instance
for deviceIndex, deviceDOM in enumerate(deviceDOMList):
blockList = ''
for block in paramClass.getParamBlocks():
for block in paramClass.getParamMemoryBlocks():
blockList += s7template.stlBlock(paramClass.name, block.name)
deviceLabel = deviceDOM.prop('label')
stlString += s7template.generateBlock(deploy.owner, paramClass.name, paramClass.version, deploy.plcSystem, DBnumber, deviceLabel, blockList, (deviceIndex == 0),deploy.plcProtocol)
fesaLabel = deviceDOM.prop('fesa-label')
stlString += s7template.generateBlock(param.owner, paramClass.name, paramClass.version, param.controller.system, DBnumber, deviceLabel, blockList, (deviceIndex == 0), param.controller.protocol, fesaLabel)
symString += s7template.symDeviceDB(paramClass.name, paramClass.version, deviceLabel, DBnumber, (deviceIndex == 0))
DBnumber += 1
else: # BLOCK_MODE
# Generate the Data-Blocks: one DB per Block of registers
for blockIndex, block in enumerate(paramClass.getParamBlocks()):
for blockIndex, block in enumerate(paramClass.getParamMemoryBlocks()):
deviceList = ''
for deviceDOM in deviceDOMList:
deviceLabel = deviceDOM.prop('label')
deviceList += s7template.stlDevice(deviceLabel, paramClass.name, block.name)
fesaLabel = deviceDOM.prop('fesa-label')
deviceList += s7template.stlDevice(deviceLabel, paramClass.name, block.name, fesaLabel)
stlString += s7template.generateBlock(deploy.owner, paramClass.name, paramClass.version, deploy.plcSystem, DBnumber, block.name, deviceList, (blockIndex == 0),deploy.plcProtocol)
stlString += s7template.generateBlock(param.owner, paramClass.name, paramClass.version, param.controller.system, DBnumber, block.name, deviceList, (blockIndex == 0),param.controller.protocol, "")
symString += s7template.symBlockDB(paramClass.name, paramClass.version, block.name, DBnumber, (blockIndex == 0))
DBnumber += 1
# write the String PLC generated code
generateControllerFiles(sourceFolderPath,deploy,".scl",stlString,logTopics);
generateControllerFiles(sourceFolderPath,param.controller.hostName,".scl",stlString,logTopics);
# Write the String PLC generated symbols into the <.sdf> symbols file
generateControllerFiles(sourceFolderPath,deploy,".sdf",symString,logTopics);
generateControllerFiles(sourceFolderPath,param.controller.hostName,".sdf",symString,logTopics);
#-------------------------------------------------------------------------
# SCHNEIDER PLC code generation
#-------------------------------------------------------------------------
# ----------------------------------------------------
def generateSchneiderRegisters(xsydoc, paramClass, deviceDOM, block, deviceIndex, modeDevice, deploy, logTopics):
def generateSchneiderRegisters(xsydoc, paramClass, deviceDOM, block, deviceIndex, modeDevice, param, logTopics):
deviceLabel = deviceDOM.prop('label')
#Device and Block adresses come from the generated Paramerers file. Depends on the protocol mode:
#DEVICE_MODE: relative address of the element within it parent node
#BLOCK_MODE : absolute address in the entire PLC memory
deviceAddress = long(deviceDOM.prop('address'))
deviceAddress = int(deviceDOM.prop('address'))
for register in block.getParamRegisters():
# If PLC does not supports this register format abort this generation and log the error
......@@ -235,7 +237,7 @@ def generateSchneiderRegisters(xsydoc, paramClass, deviceDOM, block, deviceIndex
dataElt = xsydoc.xpathEval("//dataBlock")[0] #only one dataBlock node has been inserted so far
regElt = libxml2.newNode('variables')
# Compute the base checksum of the class name
classNameCRC = zlib.crc32(paramClass.name,0) & 0xffff
classNameCRC = zlib.crc32((paramClass.name).encode(),0) & 0xffff
# Create register name = regname_crc32(classname)_deviceid
# 24 char (register name) + 1 (underscore) + 4 char (CRC classname) + 1 (underscore) + n (device id) = 30 + n (device id)
# possible problem when device id (n)> 99 --> string > 32 not compatible with Schneider PLCs
......@@ -250,10 +252,10 @@ def generateSchneiderRegisters(xsydoc, paramClass, deviceDOM, block, deviceIndex
regElt.setProp("topologicalAddress", "%%MW%ld" %totalAddress)
if block.name == 'hdrBlk':
initElt = libxml2.newNode('variableInit')
if register.name == '_version': initElt.setProp("value", deploy.silecsVersion )
if register.name == '_user' : initElt.setProp("value", deploy.owner)
if register.name == '_checksum': initElt.setProp("value", "%s" %deploy.checksum)
if register.name == '_date' : initElt.setProp("value", getDateTime())
if register.name == '_version': initElt.setProp("value", param.silecsVersion )
if register.name == '_user' : initElt.setProp("value", param.owner)
if register.name == '_checksum': initElt.setProp("value", "%s" %param.checksum)
if register.name == '_date' : initElt.setProp("value", getDateTime(param.controller))
regElt.addChild(initElt)
commElt = libxml2.newNode('comment')
commElt.setContent(paramClass.name+"/"+deviceLabel+"/"+block.name)
......@@ -261,12 +263,7 @@ def generateSchneiderRegisters(xsydoc, paramClass, deviceDOM, block, deviceIndex
dataElt.addChild(regElt)
# ----------------------------------------------------
def generateSchneiderSources(paramDOM,sourceFolderPath,logTopics):
#Base address provided by the user in the Deployment relies on the PLC model:
#Quantum, Premium and M340 Schneider PLCs are all interpreted as using 16-bit addressing,
#even though M340 addresses are later properly considered as 32-bit addresses
deploy = getDeploymentInformation(paramDOM)
def generateSchneiderSources(param,sourceFolderPath,logTopics):
# Create the Schneider DOM source (Unity <.XSY> XML document)
xsyDoc = libxml2.newDoc(version='1.0')
......@@ -275,91 +272,80 @@ def generateSchneiderSources(paramDOM,sourceFolderPath,logTopics):
dataElt = libxml2.newNode('dataBlock')
rootElt.addChild(dataElt)
for paramClass in ParamClass.getParamClassesFromRootNode(paramDOM):
for paramClass in param.controller.paramClasses:
deviceDOMList = paramClass.getDeviceInstanceNodes() # get Device instances of that class
for block in paramClass.getParamBlocks():
for block in paramClass.getParamMemoryBlocks():
for deviceIndex,deviceDOM in enumerate(deviceDOMList):
if deploy.plcProtocol == 'DEVICE_MODE': #-------------------
generateSchneiderRegisters(xsyDoc, paramClass, deviceDOM, block, deviceIndex, True, deploy, logTopics)
if param.controller.protocol == 'DEVICE_MODE': #-------------------
generateSchneiderRegisters(xsyDoc, paramClass, deviceDOM, block, deviceIndex, True, param, logTopics)
else:
generateSchneiderRegisters(xsyDoc, paramClass, deviceDOM, block, deviceIndex, False, deploy, logTopics)
generateSchneiderRegisters(xsyDoc, paramClass, deviceDOM, block, deviceIndex, False, param, logTopics)
# Finally, write the DOM object (PLC generated code) into the XSY source file
generateControllerFiles(sourceFolderPath,deploy,".xsy",rootElt.serialize(format = True),logTopics);
generateControllerFiles(sourceFolderPath,param.controller.hostName,".xsy",rootElt.serialize(format = True),logTopics);
xsyDoc.freeDoc()
# ----------------------------------------------------
def generateRabbitSources(paramDOM,sourceFolderPath,logTopics):
def generateDIGISources(param,sourceFolderPath,logTopics):
deviceLabel = ''
deploy = getDeploymentInformation(paramDOM)
# Prepare the source (<.h> file) with its diagnostic data-block header
cCodeString = rabbitTemplate.cHeader(deploy.address,deploy.silecsVersion)
cCodeString = DIGITemplate.cHeader(param.silecsVersion, param.controller.address[MEM_TYPE])
cTypeDefinitions = ''
cDataAllocation = rabbitTemplate.cAllocationComment(deploy.plcProtocol)
cDataAllocation = DIGITemplate.cAllocationComment(param.controller.protocol)
blockList = ''
classList = ''
NBdeviceDefinitionList = ''
for paramClass in ParamClass.getParamClassesFromRootNode(paramDOM):
deviceDOMList = paramClass.getDeviceInstanceNodes() # get Device instances of that class
deviceNumber = len(deviceDOMList)
#==== Generate the Blocks definition ====
for blockIndex, block in enumerate(paramClass.getParamBlocks()):
for paramClass in param.controller.paramClasses:
cTypeDefinitions += DIGITemplate.cFirstBlockUDT(paramClass.name, paramClass.version)
for block in paramClass.getParamMemoryBlocks():
registerList = ''
for register in block.getParamRegisters():
# If PLC does not supports this register format abort this generation and log the error
if(register.format in ['float64']):
iecommon.logError('ERROR: In register '+ register.name +', '+register.format+' format not supported for current controller model.', True,logTopics)
if(register.format == 'string'):
registerList += rabbitTemplate.cRegister(register.name, register.format, register.dim1, register.dim2, long(register.stringLength))
if register.format == 'float64':
iecommon.logError('ERROR: In register '+register.name+', '+ register.format +' format not supported for current controller model.', True,logTopics)
if register.format == 'string':
registerList += DIGITemplate.cRegister(register.name, register.format, register.dim1, register.dim2, register.stringLength)
else:
registerList += rabbitTemplate.cRegister(register.name, register.format, register.dim1, register.dim2)
registerList += DIGITemplate.cRegister(register.name, register.format, register.dim1, register.dim2)
cTypeDefinitions += rabbitTemplate.cBlockUDT(paramClass.name, paramClass.version, block.name, registerList, (blockIndex == 0))
cTypeDefinitions += DIGITemplate.cBlockUDT(paramClass.name, block.name, registerList)
#==== Memory allocation ====
if deploy.plcProtocol == 'DEVICE_MODE':
if param.controller.protocol == 'DEVICE_MODE':
# Generate the List of block in the class
blockList =''
for block in paramClass.getParamBlocks():
blockList += rabbitTemplate.cDeviceModeBlockInstantiation(paramClass.name, block.name)
for block in paramClass.getParamMemoryBlocks():
blockList += DIGITemplate.cDeviceModeBlockInstantiation(paramClass.name, block.name)
deviceList = ''
for deviceDOM in deviceDOMList:
for deviceDOM in paramClass.getDeviceInstanceNodes():
deviceLabel = paramClass.name + "_" + deviceDOM.prop('label')
deviceList += deviceLabel + ', ';
deviceList = deviceList[:-2] # remove last comma space
classList += rabbitTemplate.cDeviceModeClass_deviceList(blockList, deviceList)
classList += DIGITemplate.cDeviceModeClass_deviceList(blockList, deviceList)
else: # BLOCK_MODE
# Generate the List of block in the class
for block in paramClass.getParamBlocks():
for block in paramClass.getParamMemoryBlocks():
# DeviceList
deviceList = ''
for deviceDOM in deviceDOMList:
for deviceDOM in paramClass.getDeviceInstanceNodes():
deviceLabel = deviceDOM.prop('label')
deviceList +=rabbitTemplate.cBlockModeDeviceInstantiation_deviceList(paramClass.name, block.name, deviceLabel)
blockList += rabbitTemplate.cBlockModeBlockInstantiation(deviceList, paramClass.name, block.name)
deviceList +=DIGITemplate.cBlockModeDeviceInstantiation_deviceList(paramClass.name, block.name, deviceLabel)
# allocation is the same with device list or device number
blockList += DIGITemplate.cBlockModeBlockInstantiation(deviceList, paramClass.name, block.name)
# Predefine number of devices constant
cDataAllocation += NBdeviceDefinitionList
if deploy.plcProtocol == 'DEVICE_MODE':
cDataAllocation += rabbitTemplate.cDeviceModeDataInstantiation(classList)
cInitFunction = rabbitTemplate.cInitDeviceMode(deploy.silecsVersion,deploy.checksum,deploy.owner)
if param.controller.protocol == 'DEVICE_MODE':
cDataAllocation += DIGITemplate.cDeviceModeDataInstantiation(classList)
cInitFunction = DIGITemplate.cInitDeviceMode(param.silecsVersion,param.checksum,param.owner)
else:
cDataAllocation += rabbitTemplate.cBlockModeDataInstantiation(blockList)
cInitFunction = rabbitTemplate.cInitBlockMode(deploy.silecsVersion,deploy.checksum,deploy.owner)
cDataAllocation += DIGITemplate.cBlockModeDataInstantiation(blockList)
cInitFunction = DIGITemplate.cInitBlockMode(param.silecsVersion,param.checksum,param.owner)
# Append data definitions
cCodeString += cTypeDefinitions
......@@ -370,28 +356,24 @@ def generateRabbitSources(paramDOM,sourceFolderPath,logTopics):
cCodeString += cInitFunction
# Generate and append sample example
cCodeString += rabbitTemplate.cExample(deploy.plcProtocol,True,paramClass.name, block.name, register.name, deviceLabel)
cCodeString += DIGITemplate.cExample(param.controller.protocol,paramClass.name, block.name, register.name, deviceLabel)
# Finally, write the String C generated code into the temporal output file
generateControllerFiles(sourceFolderPath,deploy,".h",cCodeString,logTopics);
generateControllerFiles(sourceFolderPath,param.controller.hostName,".h",cCodeString,logTopics);
# ----------------------------------------------------
def generateVirtualS7Sources(paramDOM,sourceFolderPath,logTopics):
deploy = getDeploymentInformation(paramDOM)
def generateVirtualS7Sources(param,sourceFolderPath,logTopics):
includeDesign = ''
allocDesign = ''
deleteDesign = ''
getDesign = ''
protocolMode = 'BlockMode';
if deploy.plcProtocol == 'DEVICE_MODE':
protocolMode = 'DeviceMode';
duConstructor = virtualS7Template.vs7DuConstructor(deploy.plcName, protocolMode, deploy.address)
# Generate sources for each class that is deployed in the PLC
for paramClass in ParamClass.getParamClassesFromRootNode(paramDOM):
if param.controller.protocol == 'DEVICE_MODE':
protocolMode = 'DeviceMode';
duConstructor = virtualS7Template.vs7DuConstructor(param.deployName, param.deployVersion, protocolMode, param.controller.address[MEM_TYPE])
for paramClass in param.controller.paramClasses:
blocksCodeString = ''
createBlockCodeString = ''
......@@ -401,9 +383,7 @@ def generateVirtualS7Sources(paramDOM,sourceFolderPath,logTopics):
createDeviceCodeString = ''
deleteDeviceCodeString = ''
designCodeString = ''
deviceDOMList = paramClass.getDeviceInstanceNodes() # get Device instances of that class
#DEPLOY-UNIT code generation ============================
includeDesign += virtualS7Template.vs7DuDesignInclude(paramClass.name, paramClass.version)
allocDesign += virtualS7Template.vs7DuDesignAlloc(paramClass.name, paramClass.version)
......@@ -411,7 +391,7 @@ def generateVirtualS7Sources(paramDOM,sourceFolderPath,logTopics):
getDesign += virtualS7Template.vs7DuDesignGet(paramClass.name, paramClass.version)
#CLASSES code generation ================================
for block in paramClass.getParamBlocks():
for block in paramClass.getParamMemoryBlocks():
getSetCodeString = ''
dimsCodeString = ''
dataCodeString = ''
......@@ -419,7 +399,7 @@ def generateVirtualS7Sources(paramDOM,sourceFolderPath,logTopics):
currentRegisterAddress = 0 #used to compute even/odd adress and insert align data by adding dummy registers
previousRegisterMemSize = 0 #...
dummyIndex = 0;
createBlockCodeString += virtualS7Template.vs7ClassCreateBlock(paramClass.name, block.name)
deleteBlockCodeString += virtualS7Template.vs7ClassDeleteBlock(paramClass.name, block.name)
......@@ -430,17 +410,17 @@ def generateVirtualS7Sources(paramDOM,sourceFolderPath,logTopics):
#Specific code to force 16bit alignment if not scalar 8bit register (==> insert dummy register if needed)
dummyCodeString = virtualS7Template.vs7ClassDummyRegister(currentRegisterAddress, previousRegisterMemSize, register.memSize, dummyIndex)
dataCodeString += dummyCodeString
currentRegisterAddress += register.memSize
currentRegisterAddress += register.memSize
if dummyCodeString != "":
dummyIndex += 1
currentRegisterAddress += 1
#and now insert the normal data register
dataCodeString += virtualS7Template.vs7ClassDataRegister(register.name, register.format, register.dim1, register.dim2, register.stringLength)
previousRegisterMemSize = register.memSize
previousRegisterMemSize = register.memSize
blocksCodeString += virtualS7Template.vs7ClassBlock(deploy.silecsVersion, deploy.owner, deploy.checksum, paramClass.name, block.name, getSetCodeString, block.address, dimsCodeString, dataCodeString)
for deviceIndex, deviceDOM in enumerate(deviceDOMList):
blocksCodeString += virtualS7Template.vs7ClassBlock(param.silecsVersion, param.owner, param.checksum, paramClass.name, block.name, getSetCodeString, block.address, dimsCodeString, dataCodeString)
for deviceIndex, deviceDOM in enumerate(paramClass.getDeviceInstanceNodes()):
deviceLabel = deviceDOM.prop('label')
createDeviceCodeString += virtualS7Template.vs7ClassCreateDevice(paramClass.name, deviceLabel, deviceIndex)
deleteDeviceCodeString += virtualS7Template.vs7ClassDeleteDevice(paramClass.name, deviceLabel)
......@@ -450,30 +430,42 @@ def generateVirtualS7Sources(paramDOM,sourceFolderPath,logTopics):
classCodeString = virtualS7Template.vs7ClassHeader(paramClass.name, paramClass.version, blocksCodeString, deviceCodeString, designCodeString)
# write the CLASS generated code into the temporal output file
generateControllerFiles(sourceFolderPath,deploy, "." + paramClass.name + ".h",classCodeString,logTopics);
# Finally, write the CLASS generated code into the temporal output file
sourcesFile = os.path.normpath(sourceFolderPath + "/%s_%s.h" % (paramClass.name, paramClass.version))
iecommon.logInfo("Generate PLC sources: %s" %sourcesFile,logTopics);
fdesc = open(sourcesFile, "w")
fdesc.write(classCodeString)
fdesc.close()
# Prepare the source (<.h> file) with its diagnostic data-block header
duCodeString = virtualS7Template.vs7DuHeader(deploy.plcName, duConstructor, includeDesign, allocDesign, deleteDesign, getDesign)
duCodeString = virtualS7Template.vs7DuHeader(param.controller.hostName, param.deployVersion, duConstructor, includeDesign, allocDesign, deleteDesign, getDesign)
# Write the DEPLOY-UNIT generated code into the temporal output file
generateControllerFiles(sourceFolderPath,deploy,".h",duCodeString,logTopics);
sourcesFile = os.path.normpath(sourceFolderPath + "/%s_%s.h" % (param.deployName, param.deployVersion))
iecommon.logInfo("Generate PLC sources: %s" %sourcesFile,logTopics);
fdesc = open(sourcesFile, "w")
fdesc.write(duCodeString)
fdesc.close()
# Prepare the source (<.h> file) with its diagnostic data-block header
mainCodeString = virtualS7Template.vs7MainCode(deploy.plcName)
mainCodeString = virtualS7Template.vs7MainCode(param.deployName, param.deployVersion)
# write the DEPLOY-UNIT generated code into the temporal output file
generateControllerFiles(sourceFolderPath,deploy,".cpp",mainCodeString,logTopics);
# Finally, write the DEPLOY-UNIT generated code into the temporal output file
sourcesFile = os.path.normpath(sourceFolderPath + "/%s_%s.cpp" % (param.controller.hostName, param.deployVersion))
iecommon.logInfo("Generate PLC sources: %s" %sourcesFile,logTopics);
fdesc = open(sourcesFile, "w")
fdesc.write(mainCodeString)
fdesc.close()
# ----------------------------------------------------
def generateBeckhoffRegisters(paramClass, deviceDOM, block, deviceIndex, deploy, logTopics):
def generateBeckhoffRegisters(param, paramClass, deviceDOM, block, deviceIndex, logTopics):
source = ''
deviceLabel = deviceDOM.prop('label')
#Device and Block adresses come from the generated Paramerers file. Depends on the protocol mode:
#DEVICE_MODE: relative address of the element within it parent node
#BLOCK_MODE : absolute address in the entire PLC memory
deviceAddress = long(deviceDOM.prop('address'))
deviceAddress = int(deviceDOM.prop('address'))
for register in block.getParamRegisters():
# If PLC does not supports this register format abort this generation and log the error
......@@ -485,28 +477,28 @@ def generateBeckhoffRegisters(paramClass, deviceDOM, block, deviceIndex, deploy,
# Compute the register address relying on the Class Parameters file data
# Attention! Beckhoff uses WORD addressing while Parameters data are expressed in bytes to be PLC independent
if deploy.plcModel == 'BC9020':
totalAddress = (block.address - long(deploy.address) + (deviceIndex * block.memSize) + register.address)/2
elif deploy.plcModel == 'CX9020':
totalAddress = (block.address - long(deploy.address) + (deviceIndex * block.memSize) + register.address)
if param.controller.model == 'BC9020':
totalAddress = (block.address - int(param.controller.address[MEM_TYPE]) + (deviceIndex * block.memSize) + register.address)/2
elif param.controller.model == 'CX9020':
totalAddress = (block.address - int(param.controller.address[MEM_TYPE]) + (deviceIndex * block.memSize) + register.address)
else:
raise "PLC model not supported: " + deploy.plcModel
raise "PLC model not supported: " + param.controller.model
# add comment to source to identify block
source += ' (*'+paramClass.name+'/'+deviceLabel+'/'+block.name+' *)\r\n'
# Compute the base checksum of the class name
classNameCRC = zlib.crc32(paramClass.name,0) & 0xffff
classNameCRC = zlib.crc32((paramClass.name).encode(),0) & 0xffff
if block.name == 'hdrBlk':
if register.name == '_version':
source+=beckhoffRegInit %(register.name, classNameCRC, deviceLabel, totalAddress, whichTwincatFormat[register.format]+"(16)", deploy.silecsVersion )
source+=beckhoffRegInit %(register.name, classNameCRC, deviceLabel, int(totalAddress), whichTwincatFormat[register.format]+"(16)", param.silecsVersion )
if register.name == '_user':
source+=beckhoffRegInit %(register.name, classNameCRC, deviceLabel, totalAddress, whichTwincatFormat[register.format]+"(16)", "'"+deploy.owner+"'")
source+=beckhoffRegInit %(register.name, classNameCRC, deviceLabel, int(totalAddress), whichTwincatFormat[register.format]+"(16)", "'"+param.owner+"'")
if register.name == '_checksum':
source+=beckhoffRegInit %(register.name, classNameCRC, deviceLabel, totalAddress, whichTwincatFormat[register.format], deploy.checksum)
source+=beckhoffRegInit %(register.name, classNameCRC, deviceLabel, int(totalAddress), whichTwincatFormat[register.format], param.checksum)
if register.name == '_date':
source+=beckhoffRegInit %(register.name, classNameCRC, deviceLabel, totalAddress, whichTwincatFormat[register.format], getDateTime())
source+=beckhoffRegInit %(register.name, classNameCRC, deviceLabel, int(totalAddress), whichTwincatFormat[register.format], getDateTime(param.controller))
else: # not header block
# set data type - for string with fixed value registerLength
......@@ -516,93 +508,77 @@ def generateBeckhoffRegisters(paramClass, deviceDOM, block, deviceIndex, deploy,
format = whichTwincatFormat[register.format]
if register.dim1 == 1 and register.dim2 == 1: # scalar
source += beckhoffReg %(register.name, classNameCRC, deviceLabel, totalAddress, format)
source += beckhoffReg %(register.name, classNameCRC, deviceLabel, int(totalAddress), format)
elif register.dim1 > 1 and register.dim2 == 1: # array
source += beckhoffRegArray %(register.name, classNameCRC, deviceLabel, totalAddress, register.dim1-1, format)
source += beckhoffRegArray %(register.name, classNameCRC, deviceLabel, int(totalAddress), register.dim1-1, format)
else: # dim1>=1 and for whatever dim2, use double array syntax
source += beckhoffRegArray2d %(register.name, classNameCRC, deviceLabel, totalAddress, register.dim1-1, register.dim2-1, format)
source += beckhoffRegArray2d %(register.name, classNameCRC, deviceLabel, int(totalAddress), register.dim1-1, register.dim2-1, format)
return source
# ----------------------------------------------------
def generateBeckhoffSources(paramDOM, sourceFolderPath, logTopics):
deploy = getDeploymentInformation(paramDOM)
def generateBeckhoffSources(param, sourceFolderPath, logTopics):
# Prepare the global variables source (<.exp> file) with its diagnostic data-block header
source ="""(* +-------------------------------------------------------------------\r\n"""
source += """* | C.E.R.N Geneva, Switzerland\r\n"""
source += """* | SILECS - BE/CO-FE\r\n"""
source += """* | April 2015\r\n"""
source += """* | This file is auto generated by the SILECS framework tools.\r\n"""
source += """* | Code regeneration will overwrite it.\r\n"""
source += """* +-------------------------------------------------------------------\r\n"""
source += """*\r\n"""
source += """* Release : SILECS_%s\r\n"""%(deploy.silecsVersion)
source += """* Release : SILECS_%s\r\n"""%(param.silecsVersion)
source += """*)"""
source += '\r\nVAR_GLOBAL\r\n'
for paramClass in ParamClass.getParamClassesFromRootNode(paramDOM):
for paramClass in param.controller.paramClasses:
deviceDOMList = paramClass.getDeviceInstanceNodes() # get Device instances of that class
# Device mode is not supported for Beckhoff PLCs - Only block mode available
for block in paramClass.getParamBlocks():
for block in paramClass.getParamMemoryBlocks():
for deviceIndex, deviceDOM in enumerate(deviceDOMList):
source += generateBeckhoffRegisters(paramClass, deviceDOM, block, deviceIndex,deploy,logTopics)
source += generateBeckhoffRegisters(param, paramClass, deviceDOM, block, deviceIndex,logTopics)
source += 'END_VAR'
# Write the source into the EXP source file
generateControllerFiles(sourceFolderPath,deploy,".exp",source,logTopics);
generateControllerFiles(sourceFolderPath,param.controller.hostName,".exp",source,logTopics)
#-------------------------------------------------------------------------
# NI code generation
#-------------------------------------------------------------------------
def generateNISources(paramDOM, sourceFolderPath ,logTopics):
def generateNISources(param, sourceFolderPath ,logTopics):
iecommon.logInfo("PLC source generation not available for National Instruments equipment",logTopics)
return
def getDeploymentInformation(paramDOM):
deploy = DeploymentInfo()
mapping = paramDOM.xpathEval("/SILECS-Param/SILECS-Mapping")[0]
deploy.plcModel = mapping.prop('plc-model')
deploy.plcSystem = mapping.prop('plc-system')
deploy.plcProtocol = mapping.prop('protocol')
deploy.plcName = mapping.prop('plc-name')
deploy.address = mapping.prop('address')
ownerNode = paramDOM.xpathEval("/SILECS-Param/Mapping-Info/Owner")[0]
deploy.owner = ownerNode.prop('user-login')
deploymentNode = paramDOM.xpathEval("/SILECS-Param/Mapping-Info/Deployment")[0]
deploy.checksum = long(deploymentNode.prop('checksum'))
rootNode = paramDOM.xpathEval("/SILECS-Param")[0]
deploy.silecsVersion = rootNode.prop("silecs-version")
return deploy
def generateControllerFiles(sourceFolderPath,deploy,fileExtention,source,logTopics={'errorlog': True}):
fileName = deploy.plcName + fileExtention
def generateControllerFiles(sourceFolderPath,hostName,fileExtention,source,logTopics={'errorlog': True}):
fileName = hostName + fileExtention
fullFilePath = os.path.normpath(sourceFolderPath + "/" + fileName )
iecommon.logInfo("Generate PLC sources: %s" %fullFilePath,logTopics);
iecommon.logInfo("Generate PLC sources: %s" %fullFilePath,logTopics)
fdesc = open(fullFilePath, "w")
fdesc.write(source)
fdesc.close()
# Needed for unit-testing
def generateControllerCode(controller, paramsFile, sourceFolderPath, logTopics={'errorlog': True}):
paramDOM = libxml2.parseFile(paramsFile)
def generateControllerCode(controller, param, sourceFolderPath, logTopics={'errorlog': True}):
if controller.plcNode.get_name() == 'Siemens-PLC':
generateSiemensSources(paramDOM,sourceFolderPath,logTopics)
generateSiemensSources(param,sourceFolderPath,logTopics)
elif controller.plcNode.get_name() == 'PC-Controller':
generatePCLikeSources(param,sourceFolderPath,logTopics)
elif controller.plcNode.get_name() == 'Virtual-Controller':
generateVirtualS7Sources(paramDOM,sourceFolderPath,logTopics)
generateVirtualS7Sources(param,sourceFolderPath,logTopics)
elif controller.plcNode.get_name() == 'Schneider-PLC':
generateSchneiderSources(paramDOM,sourceFolderPath,logTopics)
generateSchneiderSources(param,sourceFolderPath,logTopics)
elif controller.plcNode.get_name() == 'Rabbit-uC':
generateRabbitSources(paramDOM,sourceFolderPath,logTopics)
generateDIGISources(param,sourceFolderPath,logTopics)
elif controller.plcNode.get_name() == 'Beckhoff-PLC':
generateBeckhoffSources(paramDOM,sourceFolderPath,logTopics)
generateBeckhoffSources(param,sourceFolderPath,logTopics)
elif controller.plcNode.get_name() == 'NI-Controller':
generateNISources(paramDOM,sourceFolderPath,logTopics)
generateNISources(param,sourceFolderPath,logTopics)
else:
iecommon.logError("The PLC-Type %s is not supported" %plcNode.get_name(), True,logTopics)
paramDOM.freeDoc()
iecommon.logError("The PLC-Type: '" + controller.plcNode.get_name() + "' is not supported", True,logTopics)
#=========================================================================
# Entry Point
......@@ -619,7 +595,13 @@ def genPlcSrc(workspacePath, deployName,silecsVersion,logTopics={'errorlog': Tru
silecsDeploy = Deploy.getDeployFromFile(deployFilePath)
for controller in silecsDeploy.controllers:
paramsFile = iefiles.getParameterFile(workspacePath, deployName,controller.hostName)
generateControllerCode(controller,paramsFile,sourceFolderPath,logTopics)
paramDOM = libxml2.parseFile(paramsFile)
param = Param()
param.initFromXMLNode(paramDOM)
param.deployName = silecsDeploy.name
param.deployVersion = silecsDeploy.version
generateControllerCode(controller,param,sourceFolderPath,logTopics)
paramDOM.freeDoc()
return "Genplcsrc succeded!"
......@@ -20,7 +20,6 @@ import os
import sys
import getpass
import codecs
import commands
import iecommon
import libxml2
......@@ -38,7 +37,7 @@ def logError(msg, exit,logTopics={}):
if 'errorlog' in logTopics:
if logTopics['errorlog'] == True:
_logMsg = "%s%s" %(logHeader("ERROR"), msg)
print _logMsg
print(_logMsg)
if exit:
raise Exception(_logMsg)
......@@ -46,13 +45,13 @@ def logInfo(msg,logTopics={}):
if 'infolog' in logTopics:
if logTopics['infolog'] == True:
_logMsg = "%s%s" %(logHeader("INFO"), msg)
print _logMsg
print(_logMsg)
def logDebug(msg,logTopics={}):
if 'debuglog' in logTopics:
if logTopics['debuglog'] == True:
_logMsg = "%s%s" %(logHeader("DEBUG"), msg)
print _logMsg
print(_logMsg)
# Append message to a text file
def logToFile(path,msg):
......@@ -143,7 +142,7 @@ def getOrCreateNamedFirstChild(parent,elementName, attributeNameValue):
return newNode
def fillAttributes(element, attrs):
for name,value in attrs.iteritems():
for name,value in attrs.items():
if not type(value) is str:
raise Exception("Error: Wrong Type Parsed for attribute: " + name)
element.setProp(name, value)
......@@ -173,4 +172,9 @@ def getPatchSilecsVersion(silecsVersionString):
secondDot = silecsVersionString.find('.',firstDot)
return silecsVersionString[secondDot:]
def supportsDateTimeLong(controller):
if controller.brand != "SIEMENS":
return False
if controller.model in ['SIMATIC_S7-300', 'SIMATIC_S7-400']:
return False
return True
......@@ -26,9 +26,9 @@ designFormat = '.silecsdesign'
deployFormat = '.silecsdeploy'
paramFormat = '.silecsparam'
def loadSilecsDesignDOM(workspacePath, silecsDesign, silecsVersion, funcGetSilecsDesignFilePath):
def loadSilecsDesignDOM(workspacePath, silecsDesign, silecsVersion, funcGetSilecsDesignFilePath, datetime_format=xmltemplate.DATE_AND_TIME):
if silecsDesign.name == "SilecsHeader":
silecsHeader = xmltemplate.getSilecsHeader(silecsVersion)
silecsHeader = xmltemplate.getSilecsHeader(silecsVersion, datetime_format)
return libxml2.parseDoc(silecsHeader)
else:
designPath = funcGetSilecsDesignFilePath(workspacePath, silecsDesign.name)
......@@ -126,6 +126,27 @@ def getDuWrapperFile(workspacePath, deployName, controllerName ):
def getDuDesignWrapperFile(workspacePath, deployName, designName):
return os.path.join(getDuWrapperSourceDirectory(workspacePath, deployName), getDuWrapperFileName(designName))
#--- DU VIRTUAL-S7 ---
def getDuVirtualS7FileName(deployName, deployVersion):
str = iecommon.capitalizeString(deployName) + "_" + deployVersion
return str.replace(".", "_") + ".h"
def getDuVirtualS7SourceDirectory(workspacePath, deployName, deployVersion):
controllerPath = workspacePath + '/' + getDeployDirectoryName(deployName, deployVersion) + '/' \
+ ieglobal.generatedDir + '/' + ieglobal.controllerDir
controllerNorm = os.path.normpath(controllerPath)
return controllerNorm
def getDuVirtualS7File(workspacePath, deployName, deployVersion):
return getDuVirtualS7SourceDirectory(workspacePath, deployName, deployVersion) + "/" \
+ getDuVirtualS7FileName(deployName, deployVersion)
def getDuDesignVirtualS7File(workspacePath, deployName, deployVersion, designName, designVersion):
return getDuVirtualS7SourceDirectory(workspacePath, deployName, deployVersion) + "/" \
+ getDuVirtualS7FileName(designName, designVersion)
#--- General ---
def getFesa3CommonDirectory(workspacePath, designName):
fesa3ClassPath = workspacePath + '/' + designName + '/src/' + designName + '/Common'
fesa3ClassPathNorm = os.path.normpath(fesa3ClassPath)
......
......@@ -16,6 +16,7 @@
import os
import sys
import ParseMigrationArgs
from migrationBase import MigrationBase
from migration0_10_0to1_0_0.migrators import *
......@@ -26,8 +27,8 @@ import FileUtils
import shutil
class Migration(MigrationBase):
def __init__(self, arguments):
super(Migration, self).__init__()
def __init__(self, silecsDocument, xmlSchema, versionOld, versionNew, createBackup):
super(Migration, self).__init__(silecsDocument, xmlSchema, versionOld, versionNew, createBackup)
def migrateClass(self, context, projectDir ):
modified = designGenerateFesaPropValueItemMigrator(context)
......@@ -42,9 +43,7 @@ class Migration(MigrationBase):
return modified
def migrateFESAInstanceFile(self):
results = self.parser.parse_args()
silecsDocument = results.silecsDocument
projectDir = FileUtils.getProjectDir(silecsDocument)
projectDir = FileUtils.getProjectDir(self.silecsDocument)
testFolder = projectDir + "/src/test"
if not os.path.isdir(testFolder):
return
......@@ -60,7 +59,18 @@ class Migration(MigrationBase):
if fesaInstanceFileMigrator(context):
self._saveAndBackupFile(context,instanceFile)
if __name__ == "__main__":
migration = Migration(sys.argv)
def main_parse():
arguments = ParseMigrationArgs.parse_arguments()
run_migrate(arguments.silecsDocument,
arguments.xmlSchema,
arguments.versionOld,
arguments.versionNew,
arguments.createBackup)
def run_migrate(silecsDocument, xmlSchema, versionOld, versionNew, createBackup):
migration = Migration(silecsDocument, xmlSchema, versionOld, versionNew, createBackup)
migration.migrate()
migration.backupOldFESAMakeSpecific()
if __name__ == "__main__":
main_parse()
......@@ -16,6 +16,7 @@
import os
import sys
import ParseMigrationArgs
from migrationBase import MigrationBase
from migration_0_9_0to0_10_0.migrateDeployDeviceNumber import DeployDeviceNumberMigrator
......@@ -25,8 +26,8 @@ import libxml2
import sys
class Migration(MigrationBase):
def __init__(self, arguments):
super(Migration, self).__init__()
def __init__(self, silecsDocument, xmlSchema, versionOld, versionNew, createBackup):
super(Migration, self).__init__(silecsDocument, xmlSchema, versionOld, versionNew, createBackup)
self._deployDomainMigrator = DeployDomainMigrator()
self._migrateDeployDeviceNumber = DeployDeviceNumberMigrator()
......@@ -39,6 +40,17 @@ class Migration(MigrationBase):
modified = self._migrateDeployDeviceNumber.migrate(context)
return modified
if __name__ == "__main__":
migration = Migration(sys.argv)
def main_parse():
arguments = ParseMigrationArgs.parse_arguments()
run_migrate(arguments.silecsDocument,
arguments.xmlSchema,
arguments.versionOld,
arguments.versionNew,
arguments.createBackup)
def run_migrate(silecsDocument, xmlSchema, versionOld, versionNew, createBackup):
migration = Migration(silecsDocument, xmlSchema, versionOld, versionNew, createBackup)
migration.migrate()
if __name__ == "__main__":
main_parse()
......@@ -16,6 +16,7 @@
import os
import sys
import ParseMigrationArgs
from migrationBase import MigrationBase
from migration1_0_Xto2_0_0.migrators import *
......@@ -26,16 +27,26 @@ import FileUtils
import shutil
class Migration(MigrationBase):
def __init__(self, arguments):
super(Migration, self).__init__()
def __init__(self, silecsDocument, xmlSchema, versionOld, versionNew, createBackup):
super(Migration, self).__init__(silecsDocument, xmlSchema, versionOld, versionNew, createBackup)
def migrateClass(self, context, projectDir ):
def migrateClass(self, context, projectDir):
modified = designValueTypeMigrator(context)
modified |= designBlockRegisterMigrator(context)
return modified
def main_parse():
arguments = ParseMigrationArgs.parse_arguments()
run_migrate(arguments.silecsDocument,
arguments.xmlSchema,
arguments.versionOld,
arguments.versionNew,
arguments.createBackup)
if __name__ == "__main__":
migration = Migration(sys.argv)
def run_migrate(silecsDocument, xmlSchema, versionOld, versionNew, createBackup):
migration = Migration(silecsDocument, xmlSchema, versionOld, versionNew, createBackup)
migration.migrate()
migration.backupOldFESAMakeSpecific()
if __name__ == "__main__":
main_parse()
......@@ -16,6 +16,7 @@
import os
import sys
import ParseMigrationArgs
from migrationBase import MigrationBase
from migration2_0_Xto2_1_X.migrators import *
......@@ -26,11 +27,11 @@ import FileUtils
import shutil
class Migration(MigrationBase):
def __init__(self, arguments):
super(Migration, self).__init__()
def __init__(self, silecsDocument, xmlSchema, versionOld, versionNew, createBackup):
super(Migration, self).__init__(silecsDocument, xmlSchema, versionOld, versionNew, createBackup)
def migrateClass(self, context, projectDir ):
modified = false
modified = False
# fix reverted, since FESA does not allow to have code outside of the "src" folder
#modified = fesaClassIncludeHeaderMigrator(context,self.silecsDocument)
......@@ -52,8 +53,17 @@ class Migration(MigrationBase):
self.updateFESAMakeSpecific()
return modified
def main_parse():
arguments = ParseMigrationArgs.parse_arguments()
run_migrate(arguments.silecsDocument,
arguments.xmlSchema,
arguments.versionOld,
arguments.versionNew,
arguments.createBackup)
if __name__ == "__main__":
migration = Migration(sys.argv)
def run_migrate(silecsDocument, xmlSchema, versionOld, versionNew, createBackup):
migration = Migration(silecsDocument, xmlSchema, versionOld, versionNew, createBackup)
migration.migrate()
if __name__ == "__main__":
main_parse()
......@@ -50,13 +50,6 @@ def getFesaSourceFiles(fesaClassName, projectDir):
for root, subdirs, files in os.walk(fesaCodeFolder):
for file in files:
if file.endswith(".cpp") or file.endswith(".h"):
print os.path.join(root,file)
print(os.path.join(root,file))
sourceFiles.append(os.path.join(root,file))
return sourceFiles
def replaceInFile(filePath,searchString,replaceWithString):
with open(filePath, 'r') as file :
filedata = file.read()
filedata = filedata.replace(searchString, replaceWithString)
with open(filePath, 'w') as file:
file.write(filedata)
\ No newline at end of file
from argparse import ArgumentParser
def parse_arguments():
parser = ArgumentParser(description='Migration script')
parser.add_argument("silecsDocument", action="store", help="The SILECS document to migrate")
parser.add_argument("xmlSchema", action="store", help="Path to the new schema")
parser.add_argument("versionOld", action="store", help="old silecs version")
parser.add_argument("versionNew", action="store", help="new silecs version")
parser.add_argument('--backup', dest='createBackup', action='store_true')
parser.add_argument('--no-backup', dest='createBackup', action='store_false')
parser.set_defaults(createBackup=False)
arguments = parser.parse_args()
return arguments
......@@ -14,12 +14,10 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from test.testBase import *
import unittest
import libxml2
#from migration.0_10_0to1_0_0 import *
from migration.migration0_10_0to1_0_0.migrators import *
import inspect #get caller name
SilecsDesignOld = '''<?xml version="1.0" encoding="UTF-8"?>
<SILECS-Design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
......@@ -90,59 +88,50 @@ SilecsDeployOldParsed = libxml2.parseDoc(SilecsDeployOld)
fesaInstanceOldParsed = libxml2.parseFile("migration/migration0_10_0to1_0_0/DeviceData_PneuDriveDU.instance")
def testdeployRemoveSilecsHeaderMigrator(deployDoc):
deployRemoveSilecsHeaderMigrator(deployDoc)
silecsHeaders = deployDoc.xpathEval('/SILECS-Deploy/Deploy-Classes/Class/name[text()="SilecsHeader"]')
assertEqual(len(silecsHeaders),0)
class TestMigration0_10_0to1_0_0(unittest.TestCase):
def testdeployReOrderControllerAndClassesMigrator(deployDoc):
deployReOrderControllerAndClassesMigrator(deployDoc)
print deployDoc
oldClasses = deployDoc.xpathEval('/SILECS-Deploy/Deploy-Classes/Class')
assertEqual(len(oldClasses),0)
oldDeployInstances = deployDoc.xpathEval('/SILECS-Deploy/Deploy-Instances')
assertEqual(len(oldDeployInstances),0)
oldPLCNode = deployDoc.xpathEval('/SILECS-Deploy/Deploy-Unit/Siemens-PLC')
assertEqual(len(oldPLCNode),0)
def test_deploySwapStep7TiaMigrator(self):
deploySwapStep7TiaMigrator(SilecsDeployOldParsed)
plcEntries = SilecsDeployOldParsed.xpathEval("/SILECS-Deploy/Deploy-Unit/Siemens-PLC[@system='TIA-PORTAL']")
self.assertEqual(len(plcEntries),1)
newContollers = deployDoc.xpathEval('/SILECS-Deploy/Controller')
assertEqual(len(newContollers),2)
for newController in newContollers:
assertTrue(newController.hasProp("host-name"))
plcNodes = newController.xpathEval('Siemens-PLC')
assertEqual(len(plcNodes),1)
classNodes = newController.xpathEval('SilecsDesign')
assertEqual(len(classNodes),2)
pneuDrive = newController.xpathEval("SilecsDesign[@silecs-design-name='PneuDrive']")[0]
pneuDriveDevices = pneuDrive.xpathEval('Device')
assertEqual(len(pneuDriveDevices),6)
whatever = newController.xpathEval('SilecsDesign[@silecs-design-name="Whatever"]')[0]
whateverDevices = whatever.xpathEval('Device')
assertEqual(len(whateverDevices),1)
deployRemoveSilecsHeaderMigrator(SilecsDeployOldParsed)
silecsHeaders = SilecsDeployOldParsed.xpathEval('/SILECS-Deploy/Deploy-Classes/Class/name[text()="SilecsHeader"]')
self.assertEqual(len(silecsHeaders), 0)
def testdeploySwapStep7TiaMigrator(deployDoc):
deploySwapStep7TiaMigrator(deployDoc)
plcEntries = deployDoc.xpathEval("/SILECS-Deploy/Deploy-Unit/Siemens-PLC[@system='TIA-PORTAL']")
assertEqual(len(plcEntries),1)
deployReOrderControllerAndClassesMigrator(SilecsDeployOldParsed)
oldClasses = SilecsDeployOldParsed.xpathEval('/SILECS-Deploy/Deploy-Classes/Class')
self.assertEqual(len(oldClasses),0)
oldDeployInstances = SilecsDeployOldParsed.xpathEval('/SILECS-Deploy/Deploy-Instances')
self.assertEqual(len(oldDeployInstances),0)
oldPLCNode = SilecsDeployOldParsed.xpathEval('/SILECS-Deploy/Deploy-Unit/Siemens-PLC')
self.assertEqual(len(oldPLCNode),0)
def testdesignGenerateFesaPropValueItemMigrator(context):
designGenerateFesaPropValueItemMigrator(context)
updatedBlocks = context.xpathEval("//Block[@generateFesaProperty='true']")
updatedRegisters = context.xpathEval("//Register[@generateFesaValueItem='true']")
assertEqual(len(updatedBlocks),2)
assertEqual(len(updatedRegisters),12)
newContollers = SilecsDeployOldParsed.xpathEval('/SILECS-Deploy/Controller')
self.assertEqual(len(newContollers),2)
for newController in newContollers:
self.assertTrue(newController.hasProp("host-name"))
plcNodes = newController.xpathEval('Siemens-PLC')
self.assertEqual(len(plcNodes),1)
classNodes = newController.xpathEval('SilecsDesign')
self.assertEqual(len(classNodes),2)
pneuDrive = newController.xpathEval("SilecsDesign[@silecs-design-name='PneuDrive']")[0]
pneuDriveDevices = pneuDrive.xpathEval('Device')
self.assertEqual(len(pneuDriveDevices),6)
whatever = newController.xpathEval('SilecsDesign[@silecs-design-name="Whatever"]')[0]
whateverDevices = whatever.xpathEval('Device')
self.assertEqual(len(whateverDevices),1)
def testfesaInstanceFileMigrator(context):
fesaInstanceFileMigrator(context)
newValues = context.xpathEval("//device-instance[@name='YR11DF3']/configuration/parameterFile/value")
assertEqual(len(newValues),1)
assertEqual(newValues[0].getContent(),"../../../generated/client/sdaplc003.silecsparam")
def runTests():
testdeploySwapStep7TiaMigrator(SilecsDeployOldParsed)
testdeployRemoveSilecsHeaderMigrator(SilecsDeployOldParsed)
testdeployReOrderControllerAndClassesMigrator(SilecsDeployOldParsed)
testdesignGenerateFesaPropValueItemMigrator(SilecsDesignOldParsed)
testfesaInstanceFileMigrator(fesaInstanceOldParsed)
def test_designGenerateFesaPropValueItemMigrator(self):
designGenerateFesaPropValueItemMigrator(SilecsDesignOldParsed)
updatedBlocks = SilecsDesignOldParsed.xpathEval("//Block[@generateFesaProperty='true']")
updatedRegisters = SilecsDesignOldParsed.xpathEval("//Register[@generateFesaValueItem='true']")
self.assertEqual(len(updatedBlocks),2)
self.assertEqual(len(updatedRegisters),12)
# print deployDoc # for debugging
def test_fesaInstanceFileMigrator(self):
fesaInstanceFileMigrator(fesaInstanceOldParsed)
newValues = fesaInstanceOldParsed.xpathEval("//device-instance[@name='YR11DF3']/configuration/parameterFile/value")
self.assertEqual(len(newValues),1)
self.assertEqual(newValues[0].getContent(),"../../../generated/client/sdaplc003.silecsparam")
#!/usr/bin/python
# Copyright 2016 CERN and GSI
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from test.testBase import *
import libxml2
from migration.migration1_0_Xto2_0_0.migrators import *
import inspect #get caller name
def testdesignValueTypeMigrator():
SilecsDesignOld = '''<?xml version="1.0" encoding="UTF-8"?>
<SILECS-Design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
silecs-version="0.10.0" created="03/21/16" updated="03/21/16"
xsi:noNamespaceSchemaLocation="/common/usr/cscofe/silecs/0.10.0/silecs-model/src/xml/DesignSchema.xsd">
<Information>
<Owner user-login="schwinn" />
<Editor user-login="schwinn" />
</Information>
<SILECS-Class name="PneuDrive" version="0.1.0" domain="TEST">
<Block name="Acq" mode="READ-ONLY">
<Register name="string1" synchro="MASTER" format="string" />
<Register name="string2" synchro="MASTER" format="string" string-len="65"/>
<Register name="string1D" synchro="MASTER" format="string" array-dim1="2" />
<Register name="string2D" synchro="MASTER" format="string" array-dim1="2" array-dim2="4"/>
<Register name="scalar" synchro="MASTER" format="int8" />
<Register name="scalar1D" synchro="MASTER" format="int8" array-dim1="2" />
<Register name="scalar2D" synchro="MASTER" format="int8" array-dim1="2" array-dim2="4"/>
</Block>
</SILECS-Class>
</SILECS-Design>'''
context = libxml2.parseDoc(SilecsDesignOld)
designValueTypeMigrator(context)
#context.shellPrintNode() #for debug
scalar = context.xpathEval("//Register[not(@format)]/scalar[@format='int8']")
array = context.xpathEval("//Register[not(@array-dim1)]/array[@dim='2' and @format='int8']")
array2D = context.xpathEval("//Register[not(@array-dim2)]/array2D[@dim1='2' and @dim2='4' and @format='int8']")
string1 = context.xpathEval("//Register/string[@format='string' and @string-length='64']")
string2 = context.xpathEval("//Register[not(@string-len)]/string[@format='string' and @string-length='65']")
stringArray = context.xpathEval("//Register/stringArray[@dim='2' and @format='string' and @string-length='64']")
stringArray2D = context.xpathEval("//Register/stringArray2D[@dim1='2' and @dim2='4' and @format='string' and @string-length='64']")
assertEqual(len(scalar),1)
assertEqual(len(array),1)
assertEqual(len(array2D),1)
assertEqual(len(string1),1)
assertEqual(len(string2),1)
assertEqual(len(stringArray),1)
assertEqual(len(stringArray2D),1)
def testdesignBlockRegisterMigrator():
SilecsDesignOld = '''<?xml version="1.0" encoding="UTF-8"?>
<SILECS-Design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
silecs-version="0.10.0" created="03/21/16" updated="03/21/16"
xsi:noNamespaceSchemaLocation="/common/usr/cscofe/silecs/0.10.0/silecs-model/src/xml/DesignSchema.xsd">
<Information>
<Owner user-login="schwinn" />
<Editor user-login="schwinn" />
</Information>
<SILECS-Class name="PneuDrive" version="0.1.0" domain="TEST">
<Block name="block1" mode="READ-ONLY">
<Register name="b1r1" synchro="MASTER" format="string" />
<Register name="b1r2" synchro="NONE" format="string" array-dim1="2" />
</Block>
<Block name="block2" mode="READ-WRITE">
<Register name="b2r1" synchro="MASTER" format="string" />
<Register name="b2r2" synchro="SLAVE" format="string" />
<Register name="b2r3" synchro="NONE" format="string" array-dim1="2" />
</Block>
<Block name="block3" mode="WRITE-ONLY">
<Register name="b3r1" synchro="SLAVE" format="string" />
<Register name="b3r2" synchro="NONE" format="string" array-dim1="2" />
</Block>
</SILECS-Class>
</SILECS-Design>'''
context = libxml2.parseDoc(SilecsDesignOld)
designBlockRegisterMigrator(context)
context.shellPrintNode() #for debug
oldBlocks = context.xpathEval("//Block")
oldRegisters = context.xpathEval("//Register")
assertEqual(len(oldBlocks),0)
assertEqual(len(oldRegisters),0)
block1 = context.xpathEval("/SILECS-Design/SILECS-Class/Acquisition-Block")
block2 = context.xpathEval("/SILECS-Design/SILECS-Class/Setting-Block")
block3 = context.xpathEval("/SILECS-Design/SILECS-Class/Command-Block")
assertEqual(len(block1),1)
assertEqual(len(block2),1)
assertEqual(len(block3),1)
b1r1 = context.xpathEval("/SILECS-Design/SILECS-Class/Acquisition-Block/Acquisition-Register[@name='b1r1']")
b1r2 = context.xpathEval("/SILECS-Design/SILECS-Class/Acquisition-Block/Acquisition-Register[@name='b1r2']")
b2r1 = context.xpathEval("/SILECS-Design/SILECS-Class/Setting-Block/Volatile-Register[@name='b2r1']")
b2r2 = context.xpathEval("/SILECS-Design/SILECS-Class/Setting-Block/Setting-Register[@name='b2r2']") # synchro = master ? --> Missconfiguration --> We should have this reg in an acquisition-block
b2r3 = context.xpathEval("/SILECS-Design/SILECS-Class/Setting-Block/Volatile-Register[@name='b2r3']")
b3r2 = context.xpathEval("/SILECS-Design/SILECS-Class/Command-Block/Setting-Register[@name='b3r1']")
b3r2 = context.xpathEval("/SILECS-Design/SILECS-Class/Command-Block/Setting-Register[@name='b3r2']")
def runTests():
testdesignValueTypeMigrator()
testdesignBlockRegisterMigrator()
# print deployDoc # for debugging
#!/usr/bin/python
# Copyright 2016 CERN and GSI
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import unittest
import libxml2
from migration.migration1_0_Xto2_0_0.migrators import *
class TestMigration1_0_Xto2_0_0(unittest.TestCase):
def test_designValueTypeMigrator(self):
SilecsDesignOld = '''<?xml version="1.0" encoding="UTF-8"?>
<SILECS-Design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
silecs-version="0.10.0" created="03/21/16" updated="03/21/16"
xsi:noNamespaceSchemaLocation="/common/usr/cscofe/silecs/0.10.0/silecs-model/src/xml/DesignSchema.xsd">
<Information>
<Owner user-login="schwinn" />
<Editor user-login="schwinn" />
</Information>
<SILECS-Class name="PneuDrive" version="0.1.0" domain="TEST">
<Block name="Acq" mode="READ-ONLY">
<Register name="string1" synchro="MASTER" format="string" />
<Register name="string2" synchro="MASTER" format="string" string-len="65"/>
<Register name="string1D" synchro="MASTER" format="string" array-dim1="2" />
<Register name="string2D" synchro="MASTER" format="string" array-dim1="2" array-dim2="4"/>
<Register name="scalar" synchro="MASTER" format="int8" />
<Register name="scalar1D" synchro="MASTER" format="int8" array-dim1="2" />
<Register name="scalar2D" synchro="MASTER" format="int8" array-dim1="2" array-dim2="4"/>
</Block>
</SILECS-Class>
</SILECS-Design>'''
context = libxml2.parseDoc(SilecsDesignOld)
designValueTypeMigrator(context)
#context.shellPrintNode() #for debug
scalar = context.xpathEval("//Register[not(@format)]/scalar[@format='int8']")
array = context.xpathEval("//Register[not(@array-dim1)]/array[@dim='2' and @format='int8']")
array2D = context.xpathEval("//Register[not(@array-dim2)]/array2D[@dim1='2' and @dim2='4' and @format='int8']")
string1 = context.xpathEval("//Register/string[@format='string' and @string-length='64']")
string2 = context.xpathEval("//Register[not(@string-len)]/string[@format='string' and @string-length='65']")
stringArray = context.xpathEval("//Register/stringArray[@dim='2' and @format='string' and @string-length='64']")
stringArray2D = context.xpathEval("//Register/stringArray2D[@dim1='2' and @dim2='4' and @format='string' and @string-length='64']")
self.assertEqual(len(scalar), 1)
self.assertEqual(len(array), 1)
self.assertEqual(len(array2D), 1)
self.assertEqual(len(string1), 1)
self.assertEqual(len(string2), 1)
self.assertEqual(len(stringArray), 1)
self.assertEqual(len(stringArray2D), 1)
def test_designBlockRegisterMigrator(self):
SilecsDesignOld = '''<?xml version="1.0" encoding="UTF-8"?>
<SILECS-Design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
silecs-version="0.10.0" created="03/21/16" updated="03/21/16"
xsi:noNamespaceSchemaLocation="/common/usr/cscofe/silecs/0.10.0/silecs-model/src/xml/DesignSchema.xsd">
<Information>
<Owner user-login="schwinn" />
<Editor user-login="schwinn" />
</Information>
<SILECS-Class name="PneuDrive" version="0.1.0" domain="TEST">
<Block name="block1" mode="READ-ONLY">
<Register name="b1r1" synchro="MASTER" format="string" />
<Register name="b1r2" synchro="NONE" format="string" array-dim1="2" />
</Block>
<Block name="block2" mode="READ-WRITE">
<Register name="b2r1" synchro="MASTER" format="string" />
<Register name="b2r2" synchro="SLAVE" format="string" />
<Register name="b2r3" synchro="NONE" format="string" array-dim1="2" />
</Block>
<Block name="block3" mode="WRITE-ONLY">
<Register name="b3r1" synchro="SLAVE" format="string" />
<Register name="b3r2" synchro="NONE" format="string" array-dim1="2" />
</Block>
</SILECS-Class>
</SILECS-Design>'''
context = libxml2.parseDoc(SilecsDesignOld)
designBlockRegisterMigrator(context)
context.shellPrintNode() #for debug
oldBlocks = context.xpathEval("//Block")
oldRegisters = context.xpathEval("//Register")
self.assertEqual(len(oldBlocks),0)
self.assertEqual(len(oldRegisters),0)
block1 = context.xpathEval("/SILECS-Design/SILECS-Class/Acquisition-Block")
block2 = context.xpathEval("/SILECS-Design/SILECS-Class/Setting-Block")
block3 = context.xpathEval("/SILECS-Design/SILECS-Class/Command-Block")
self.assertEqual(len(block1),1)
self.assertEqual(len(block2),1)
self.assertEqual(len(block3),1)
b1r1 = context.xpathEval("/SILECS-Design/SILECS-Class/Acquisition-Block/Acquisition-Register[@name='b1r1']")
b1r2 = context.xpathEval("/SILECS-Design/SILECS-Class/Acquisition-Block/Acquisition-Register[@name='b1r2']")
b2r1 = context.xpathEval("/SILECS-Design/SILECS-Class/Setting-Block/Volatile-Register[@name='b2r1']")
b2r2 = context.xpathEval("/SILECS-Design/SILECS-Class/Setting-Block/Setting-Register[@name='b2r2']") # synchro = master ? --> Missconfiguration --> We should have this reg in an acquisition-block
b2r3 = context.xpathEval("/SILECS-Design/SILECS-Class/Setting-Block/Volatile-Register[@name='b2r3']")
b3r2 = context.xpathEval("/SILECS-Design/SILECS-Class/Command-Block/Setting-Register[@name='b3r1']")
b3r2 = context.xpathEval("/SILECS-Design/SILECS-Class/Command-Block/Setting-Register[@name='b3r2']")
......@@ -105,7 +105,10 @@ def silecsDeployMigrator(silecsDocument):
silecsDesign.unlinkNode()
alreadyThere = silecsDocument.xpathEval("/SILECS-Deploy/SilecsDesign[@silecs-design-name='" + silecsDesign.prop("silecs-design-name") + "']")
if len(alreadyThere) == 0:
deployUnitNode.addNextSibling(silecsDesign)
newDesign = libxml2.newNode("SilecsDesign") #need to re-build, otherwise error for empty </SilecsDesign>
newDesign.newProp("silecs-design-version",silecsDesign.prop("silecs-design-version"))
newDesign.newProp("silecs-design-name",silecsDesign.prop("silecs-design-name"))
deployUnitNode.addNextSibling(newDesign)
modified = True
return modified
#!/usr/bin/python
# Copyright 2016 CERN and GSI
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from test.testBase import *
import libxml2
from migration.migration2_0_Xto2_1_X.migrators import *
import inspect # get caller name
from shutil import copyfile
import os
#-------------------------------------- def testFesaClassMakeSpecificMigrator():
#------------- currentDirectory = os.path.dirname(os.path.abspath(__file__))
# makefileSpecific = os.path.join(currentDirectory, "myProject", "Makefile.specific")
# makefileSpecificOriginal = os.path.join(currentDirectory, "myProject", "Makefile.specific.original")
# makefileSpecificCorrect = os.path.join(currentDirectory, "myProject", "Makefile.specific.correct")
#-------------------------------------- if os.path.isfile(makefileSpecific):
#------------------------------------------- os.remove(makefileSpecific)
#---------------------- copyfile(makefileSpecificOriginal, makefileSpecific)
#--------------------------- fesaClassMakeSpecificMigrator(makefileSpecific)
#----------------- assertFileEqual(makefileSpecific,makefileSpecificCorrect)
def testSilecsDeployMigrator():
silecsDeployDocOld = '''<?xml version="1.0" encoding="UTF-8"?>
<SILECS-Deploy silecs-version="1.0.1" created="05/03/17" updated="05/03/17" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="/common/usr/cscofe/silecs/silecs-model/1.0.1/xml/DeploySchema.xsd">
<Information>
<Owner user-login="schwinn" />
<Editor user-login="schwinn" />
</Information>
<Deploy-Unit name="AlexTestDU" version="0.1.0" />
<Controller host-name="asl733">
<Siemens-PLC system="TIA-PORTAL" model="SIMATIC_S7-300" protocol="DEVICE_MODE" base-DB-number="1" />
<SilecsDesign silecs-design-version="0.1.0" silecs-design-name="AlexTest1">
<Device device-name="myDev1" />
<Device device-name="myDev2" />
</SilecsDesign>
<SilecsDesign silecs-design-version="0.1.0" silecs-design-name="AlexTest2">
<Device device-name="myDev3" />
<Device device-name="myDev4" />
</SilecsDesign>
</Controller>
<Controller host-name="asl744">
<Siemens-PLC system="TIA-PORTAL" model="SIMATIC_S7-300" protocol="DEVICE_MODE" base-DB-number="1" />
<SilecsDesign silecs-design-version="0.1.0" silecs-design-name="AlexTest1">
<Device device-name="myDev1" />
<Device device-name="myDev2" />
</SilecsDesign>
<SilecsDesign silecs-design-version="0.1.0" silecs-design-name="AlexTest2">
<Device device-name="myDev3" />
<Device device-name="myDev4" />
</SilecsDesign>
</Controller>
</SILECS-Deploy>'''
context = libxml2.parseDoc(silecsDeployDocOld)
silecsDeployMigrator(context)
print context
silecsDesigns = context.xpathEval("/SILECS-Deploy/SilecsDesign")
assertEqual(len(silecsDesigns),2)
devices = context.xpathEval("/SILECS-Deploy/Controller[@host-name='asl733']/*/Device")
assertEqual(len(devices),4)
assertEqual(devices[0].prop("silecs-design-ref"),"AlexTest1")
assertEqual(devices[0].prop("silecs-device-label"),"myDev1")
def testremoveFesaConfigurationFieldParameterFile():
FesaDocOld = '''<?xml version="1.0" encoding="UTF-8"?>
<equipment-model xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="file:/opt/fesa/fesa-model-gsi/4.2.0/xml/design/design-gsi.xsd">
<data>
<device-data>
<configuration>
<field name="parameterFile" id="_170711152634_36"><description>ParameterFile of the PLC (*.silecsparam)</description><array type="char"><dim>512</dim></array><default>../../../generated/client/MyControllerName.silecsparam</default></field><field name="plcDeviceLabel" id="_170711152634_37"><description>Name of the related SILECS instance within the PLC mapping</description><array type="char"><dim>128</dim></array></field><field name="plcHostName" id="_170711152634_38"><description>Hostname of the PLC that contains the related SILECS class device</description><array type="char"><dim>128</dim></array></field><GSI-detailed-status-labels-field name="detailedStatus_labels" id="_170503152015_8">
<array2D type="char">
<custom-constant-dim1 constant-name-ref="DETAILED_STATUS_SIZE"/>
<custom-constant-dim2 constant-name-ref="MAX_DETAILED_STATUS_LABEL_LENGTH"/>
</array2D>
<default>{myStatusLabel1,myStatusLabel2}</default>
</GSI-detailed-status-labels-field>
<GSI-detailed-status-severity-field name="detailedStatus_severity" id="_170503152015_9">
<custom-type-array data-type-name-ref="DETAILED_STATUS_SEVERITY">
<custom-constant-dim constant-name-ref="DETAILED_STATUS_SIZE"/>
</custom-type-array>
<default>{INFO,INFO}</default>
</GSI-detailed-status-severity-field>
<GSI-module-status-labels-field name="moduleStatus_labels" id="_170503152015_10">
<array2D type="char">
<custom-constant-dim1 constant-name-ref="MODULE_STATUS_SIZE"/>
<custom-constant-dim2 constant-name-ref="MAX_MODULE_STATUS_LABEL_LENGTH"/>
</array2D>
<default>{myModule1,myModule2}</default>
</GSI-module-status-labels-field>
</configuration>
</device-data>
</data>
</equipment-model>
'''
context = libxml2.parseDoc(FesaDocOld)
removeFesaConfigurationFieldParameterFile(context)
paramFields = context.xpathEval("//field[@name='parameterFile']")
assertEqual(len(paramFields),0)
def runTests():
#testFesaClassMakeSpecificMigrator()
testremoveFesaConfigurationFieldParameterFile()
testSilecsDeployMigrator()
# print deployDoc # for debugging
#!/usr/bin/python
# Copyright 2016 CERN and GSI
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import unittest
import libxml2
from migration.migration2_0_Xto2_1_X.migrators import *
class TestMigration2_0_Xto2_1_X(unittest.TestCase):
def test_silecsDeployMigrator(self):
silecsDeployDocOld = '''<?xml version="1.0" encoding="UTF-8"?>
<SILECS-Deploy silecs-version="1.0.1" created="05/03/17" updated="05/03/17" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="/common/usr/cscofe/silecs/silecs-model/1.0.1/xml/DeploySchema.xsd">
<Information>
<Owner user-login="schwinn" />
<Editor user-login="schwinn" />
</Information>
<Deploy-Unit name="AlexTestDU" version="0.1.0" />
<Controller host-name="asl733">
<Siemens-PLC system="TIA-PORTAL" model="SIMATIC_S7-300" protocol="DEVICE_MODE" base-DB-number="1" />
<SilecsDesign silecs-design-version="0.1.0" silecs-design-name="AlexTest1">
<Device device-name="myDev1" />
<Device device-name="myDev2" />
</SilecsDesign>
<SilecsDesign silecs-design-version="0.1.0" silecs-design-name="AlexTest2">
<Device device-name="myDev3" />
<Device device-name="myDev4" />
</SilecsDesign>
</Controller>
<Controller host-name="asl744">
<Siemens-PLC system="TIA-PORTAL" model="SIMATIC_S7-300" protocol="DEVICE_MODE" base-DB-number="1" />
<SilecsDesign silecs-design-version="0.1.0" silecs-design-name="AlexTest1">
<Device device-name="myDev1" />
<Device device-name="myDev2" />
</SilecsDesign>
<SilecsDesign silecs-design-version="0.1.0" silecs-design-name="AlexTest2">
<Device device-name="myDev3" />
<Device device-name="myDev4" />
</SilecsDesign>
</Controller>
</SILECS-Deploy>'''
context = libxml2.parseDoc(silecsDeployDocOld)
silecsDeployMigrator(context)
print(context)
silecsDesigns = context.xpathEval("/SILECS-Deploy/SilecsDesign")
self.assertEqual(len(silecsDesigns), 2)
devices = context.xpathEval("/SILECS-Deploy/Controller[@host-name='asl733']/*/Device")
self.assertEqual(len(devices),4)
self.assertEqual(devices[0].prop("silecs-design-ref"), "AlexTest1")
self.assertEqual(devices[0].prop("silecs-device-label"), "myDev1")
def test_removeFesaConfigurationFieldParameterFile(self):
FesaDocOld = '''<?xml version="1.0" encoding="UTF-8"?>
<equipment-model xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="file:/opt/fesa/fesa-model-gsi/4.2.0/xml/design/design-gsi.xsd">
<data>
<device-data>
<configuration>
<field name="parameterFile" id="_170711152634_36"><description>ParameterFile of the PLC (*.silecsparam)</description><array type="char"><dim>512</dim></array><default>../../../generated/client/MyControllerName.silecsparam</default></field><field name="plcDeviceLabel" id="_170711152634_37"><description>Name of the related SILECS instance within the PLC mapping</description><array type="char"><dim>128</dim></array></field><field name="plcHostName" id="_170711152634_38"><description>Hostname of the PLC that contains the related SILECS class device</description><array type="char"><dim>128</dim></array></field><GSI-detailed-status-labels-field name="detailedStatus_labels" id="_170503152015_8">
<array2D type="char">
<custom-constant-dim1 constant-name-ref="DETAILED_STATUS_SIZE"/>
<custom-constant-dim2 constant-name-ref="MAX_DETAILED_STATUS_LABEL_LENGTH"/>
</array2D>
<default>{myStatusLabel1,myStatusLabel2}</default>
</GSI-detailed-status-labels-field>
<GSI-detailed-status-severity-field name="detailedStatus_severity" id="_170503152015_9">
<custom-type-array data-type-name-ref="DETAILED_STATUS_SEVERITY">
<custom-constant-dim constant-name-ref="DETAILED_STATUS_SIZE"/>
</custom-type-array>
<default>{INFO,INFO}</default>
</GSI-detailed-status-severity-field>
<GSI-module-status-labels-field name="moduleStatus_labels" id="_170503152015_10">
<array2D type="char">
<custom-constant-dim1 constant-name-ref="MODULE_STATUS_SIZE"/>
<custom-constant-dim2 constant-name-ref="MAX_MODULE_STATUS_LABEL_LENGTH"/>
</array2D>
<default>{myModule1,myModule2}</default>
</GSI-module-status-labels-field>
</configuration>
</device-data>
</data>
</equipment-model>
'''
context = libxml2.parseDoc(FesaDocOld)
removeFesaConfigurationFieldParameterFile(context)
paramFields = context.xpathEval("//field[@name='parameterFile']")
self.assertEqual(len(paramFields), 0)
\ No newline at end of file
......@@ -18,22 +18,17 @@ import fnmatch
import os
import sys
import libxml2
from argparse import ArgumentParser
import FileUtils
import ParseMigrationArgs
class MigrationBase(object):
def __init__(self):
self.parser = ArgumentParser(description='Migration script')
self.parser.add_argument("silecsDocument", action="store", help="The SILECS document to migrate")
self.parser.add_argument("xmlSchema", action="store", help="Path to the new schema")
self.parser.add_argument("versionOld", action="store", help="old silecs version")
self.parser.add_argument("versionNew", action="store", help="new silecs version")
results = self.parser.parse_args()
self.versionOld = results.versionOld
self.versionNew = results.versionNew
self.silecsDocument = results.silecsDocument
self.xmlSchema = results.xmlSchema
def __init__(self, silecsDocument, xmlSchema, versionOld, versionNew, createBackup=False):
self.versionOld = versionOld
self.versionNew = versionNew
self.silecsDocument = silecsDocument
self.xmlSchema = xmlSchema
self.createBackup = createBackup
def fixSilecsVersion(self, context):
root = context.xpathEval("/*")[0]
if root.prop("silecs-version") != self.versionOld:
......@@ -50,18 +45,33 @@ class MigrationBase(object):
projectDir = FileUtils.getProjectDir(self.silecsDocument)
makeSpecific = os.path.join(projectDir,"Makefile.specific")
FileUtils.backupFile(makeSpecific)
def updateFESAMakeSpecific(self):
projectDir = FileUtils.getProjectDir(self.silecsDocument)
makeSpecific = os.path.join(projectDir,"Makefile.specific")
print("Migration-Info: Old Version-Strings in '" + makeSpecific + "' will be replaced\n")
if os.path.isfile(makeSpecific):
oldComm = "silecs-communication-cpp/" + self.versionOld
newComm = "silecs-communication-cpp/" + self.versionNew
FileUtils.replaceInFile(makeSpecific,oldComm,newComm)
oldSnap7 = "snap7/" + self.versionOld
newSnap7 = "snap7/" + self.versionNew
FileUtils.replaceInFile(makeSpecific,oldSnap7,newSnap7)
print(f"Migration-Info: {makeSpecific} will be updated.\n")
if FileUtils.fileExists(makeSpecific):
included = False
with open(makeSpecific, 'r') as file:
filedata = file.read()
if "include Makefile.silecs" in filedata:
# Correct include already in file, we have nothing to do.
included = True
if not included:
lines = []
for line in filedata.split('\n'):
if ("SILECS" in line) or ("snap7" in line) or ("SNAP7" in line):
print(f"Removing line '{line}'")
else:
lines.append(line + '\n')
lines.insert(0, "include Makefile.silecs\n")
lines.insert(0, "# Include SILECS makefile\n")
with open(makeSpecific, 'w') as file:
for line in lines:
file.write(line)
def removeGenCode(self):
projectDir = FileUtils.getProjectDir(self.silecsDocument)
......@@ -100,11 +110,13 @@ class MigrationBase(object):
# Unknown
else:
raise IOError("Document type unknown: %r" % extension)
# only chaning the version or the schema does not count as modification --> no backup needed
self.fixSilecsVersion(context)
self.fixXMLScheman(context,self.xmlSchema)
if modified:
self.updateFESAMakeSpecific()
if modified and self.createBackup:
self._saveAndBackupFile(context, self.silecsDocument)
else:
self._saveFile(context, self.silecsDocument)
......@@ -134,14 +146,18 @@ class MigrationBase(object):
context.saveTo(fd)
class MigrationAny(MigrationBase):
def __init__(self, arguments):
super(MigrationAny, self).__init__()
def run_migrate(silecsDocument, xmlSchema, versionOld, versionNew, createBackup):
migration = MigrationBase(silecsDocument, xmlSchema, versionOld, versionNew, createBackup)
migration.migrate()
def main_parse():
arguments = ParseMigrationArgs.parse_arguments()
def dummy(self):
#doNothing
print "dummy"
run_migrate(arguments.silecsDocument,
arguments.xmlSchema,
arguments.versionOld,
arguments.versionNew,
arguments.createBackup)
if __name__ == "__main__":
migration = MigrationAny(sys.argv)
migration.migrate()
\ No newline at end of file
main_parse()